From 5c27fed594a3cff7c10ecebf52b064f9d8d2801b Mon Sep 17 00:00:00 2001 From: rubeniskov Date: Mon, 23 Nov 2020 23:24:51 +0100 Subject: [PATCH] feat: add traverse recursively through the same key --- index.js | 42 ++++++++++++++++++++++++++++++++++-------- test.js | 20 ++++++++++++++++++++ utils.js | 5 ++++- 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 1d032d7..a986536 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,8 @@ const { createMatcher, wrapIterator, entries, + formatJsonPath, + JSONPATH_SEP, } = require('./utils'); /** @@ -133,34 +135,57 @@ const { * [ '/nested/nested/nested/depth', 3 ] * [ '/nested/nested/nested/nested/depth', 4 ] * ``` + * __{ [test](#traversejsonoptions--object): "@nested" }__ + * ``` + * [ '/nested', + * { depth: 1, nested: { depth: 2, nested: [Object] } } ] + * [ '/nested/nested', + * { depth: 2, nested: { depth: 3, nested: [Object] } } ] + * [ '/nested/nested/nested', { depth: 3, nested: { depth: 4 } } ] + * [ '/nested/nested/nested/nested', { depth: 4 } ] + * ``` */ const traverseJson = (obj, opts) => { - const { + let { recursive = true, nested = false, test = null, step = 1, } = { ...opts }; - let filter = createMatcher(test); + let rkey; + let filter; let overall = []; let cursor = 0; + if (typeof test === 'string' && test[0] === '@') { + rkey = test.substring(1); + nested = true; + filter = false; + } else { + filter = createMatcher(test); + } + const dive = (value, prefix) => { - const remain = overall.slice(cursor + 1); + if (rkey) { + overall = value[rkey] !== undefined + ? [[formatJsonPath(prefix, rkey), value[rkey]]] + : []; + } else { + const remain = overall.slice(cursor + 1); - overall = entries(value, prefix); + overall = entries(value, prefix); - for(let i = 0; i < remain.length; i++) { - overall.push(remain[i]); + for(let i = 0; i < remain.length; i++) { + overall.push(remain[i]); + } + cursor = 0; } - return overall[cursor = 0]; }; dive(obj); const next = () => { - if (cursor < overall.length) { let entry = overall[cursor]; if (recursive) { @@ -239,3 +264,4 @@ const createIterator = (obj, opts) => { module.exports = traverseJson; module.exports.createIterator = createIterator; +module.exports.JSONPATH_SEP = JSONPATH_SEP; diff --git a/test.js b/test.js index 35f61e7..7e1290c 100644 --- a/test.js +++ b/test.js @@ -229,6 +229,26 @@ test('should iterate filtering with minimatch through the entries of the given n iterateEqual(t, ientries, expected, true); }); +test('should iterate recursively through the same key', (t) => { + + const expected = [ + ['/nested', recursiveObject.nested], + ['/nested/nested', recursiveObject.nested.nested], + ['/nested/nested/nested', recursiveObject.nested.nested.nested], + ['/nested/nested/nested/nested', recursiveObject.nested.nested.nested.nested], + ]; + + const merged = { + ...recursiveObject, ...nestedObject, + }; + + const ientries = traverseObject(merged, { + test: '@nested', + }); + + iterateEqual(t, ientries, expected, true); +}); + test('should works as iterable', (t) => { const { createIterator } = traverseObject; diff --git a/utils.js b/utils.js index 76f0c1c..00836c6 100644 --- a/utils.js +++ b/utils.js @@ -1,6 +1,8 @@ const { isPlainObject } = require('is-plain-object'); const minimatch = require('minimatch'); +const JSONPATH_SEP = '/'; + const isTraversable = (value) => Array.isArray(value) || isPlainObject(value); const createMatcher = (test) => { @@ -15,7 +17,7 @@ const createMatcher = (test) => { return test && test.test ? ([path]) => test.test(path) : test; }; -const formatJsonPath = (prefix, key) => [prefix, key].join('/'); +const formatJsonPath = (prefix, key) => [prefix, key].join(JSONPATH_SEP); /** * Wraps a function iteratior to become an iterable @@ -45,5 +47,6 @@ module.exports = { isTraversable, createMatcher, wrapIterator, + formatJsonPath, entries, };