From dbb8e6317aaed8ac5ab35af31b9b3a5739ae08be Mon Sep 17 00:00:00 2001 From: William Neely Date: Mon, 12 Feb 2018 10:15:22 -0500 Subject: [PATCH 01/11] add new functionality 1) Enable dynamic keys by evaluating any key starting with "$." 2) Enable arrays to be merged to one object --- lib/jsonpath-object-transform.js | 40 ++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/lib/jsonpath-object-transform.js b/lib/jsonpath-object-transform.js index 022db64..9b2ef9c 100644 --- a/lib/jsonpath-object-transform.js +++ b/lib/jsonpath-object-transform.js @@ -33,7 +33,11 @@ */ function walk(data, path, result, key) { var fn; - + // if the key starts with $., assume that it's dynamic and should be evaluated + if (key && key.toString().indexOf('$.')>-1) { + key = jsonPath.eval(data, key); + } + switch (type(path)) { case 'string': fn = seekSingle; @@ -72,13 +76,9 @@ * @param {string} key */ function seekSingle(data, pathStr, result, key) { - if(pathStr.indexOf('$') < 0){ - result[key] = pathStr; - }else{ - var seek = jsonPath.eval(data, pathStr) || []; + var seek = jsonPath.eval(data, pathStr) || []; - result[key] = seek.length ? seek[0] : undefined; - } + result[key] = seek.length ? seek[0] : undefined; } /** @@ -95,11 +95,16 @@ var seek = jsonPath.eval(data, path) || []; if (seek.length && subpath) { - result = result[key] = []; - + result[key] = []; seek[0].forEach(function(item, index) { - walk(item, subpath, result, index); + walk(item, subpath, result[key], index); }); + if(pathArr[2] && pathArr[2].merge) { + // merge the individual objects in the array into one big object if the merge option is set to true + result[key] = result[key].reduce((reduced, el) => { + return Object.assign(reduced, el); + }, {}); + } } else { result[key] = seek; } @@ -130,10 +135,21 @@ * @returns {object} */ return function(data, path) { - var result = {}; - + var result = {}, + needsWrapper = Array.isArray(data) && Array.isArray(path); + // wrap the data and path in a temp variable that will serve as the key for our initial iteration + // this is to resolve the fact that the code doesn't handle root level arrays natively + if (needsWrapper) { + data = { temp: data }; + path = { temp: path }; + } + walk(data, path, result); + // unwrap the data before returning it + if (needsWrapper) { + result = result.temp; + } return result; }; From 3165cdfb476fbfe422dd769749c8d9e6f615f110 Mon Sep 17 00:00:00 2001 From: William Neely Date: Mon, 12 Feb 2018 10:54:57 -0500 Subject: [PATCH 02/11] Fix issue 5 by iterating over the "seek" array. https://github.com/dvdln/jsonpath-object-transform/issues/5 --- lib/jsonpath-object-transform.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/jsonpath-object-transform.js b/lib/jsonpath-object-transform.js index 9b2ef9c..a9fe2dc 100644 --- a/lib/jsonpath-object-transform.js +++ b/lib/jsonpath-object-transform.js @@ -93,13 +93,18 @@ var subpath = pathArr[1]; var path = pathArr[0]; var seek = jsonPath.eval(data, path) || []; + var mergeArray = pathArr[2] && pathArr[2].merge; if (seek.length && subpath) { result[key] = []; - seek[0].forEach(function(item, index) { - walk(item, subpath, result[key], index); + seek.forEach(function(items, seekIndex) { + items.forEach((item, itemIndex) => { + // itemIndex/seekIndex + 1 is so we are never multiplying by 0 which would throw off where the items should go + // subtract one after getting the index to rebase to 0 + walk(item, subpath, result[key], (itemIndex + 1) * (seekIndex + 1) - 1); + }); }); - if(pathArr[2] && pathArr[2].merge) { + if(mergeArray) { // merge the individual objects in the array into one big object if the merge option is set to true result[key] = result[key].reduce((reduced, el) => { return Object.assign(reduced, el); From 4cb514484c4fc17b14e7aad977093f8a59d785db Mon Sep 17 00:00:00 2001 From: William Neely Date: Mon, 12 Feb 2018 17:03:38 -0500 Subject: [PATCH 03/11] better array/object iteration logic use the array length as the key for walking --- lib/jsonpath-object-transform.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/jsonpath-object-transform.js b/lib/jsonpath-object-transform.js index a9fe2dc..4ca194f 100644 --- a/lib/jsonpath-object-transform.js +++ b/lib/jsonpath-object-transform.js @@ -98,10 +98,13 @@ if (seek.length && subpath) { result[key] = []; seek.forEach(function(items, seekIndex) { + // if it's just a regular object, make it into an array of one so we can still iterate it + if(!Array.isArray(items)) { + items = [items]; + } items.forEach((item, itemIndex) => { - // itemIndex/seekIndex + 1 is so we are never multiplying by 0 which would throw off where the items should go - // subtract one after getting the index to rebase to 0 - walk(item, subpath, result[key], (itemIndex + 1) * (seekIndex + 1) - 1); + // result[key].length is to push each new item to the array + walk(item, subpath, result[key], result[key].length); }); }); if(mergeArray) { From e87ae331b8d93cd00f74c3595ace065fa19bff81 Mon Sep 17 00:00:00 2001 From: William Neely Date: Mon, 12 Feb 2018 17:19:04 -0500 Subject: [PATCH 04/11] Update README with new methods Fixed bugs present in #5 and added documentation for the options argument and dynamic keys --- README.md | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 223c70d..bc1dc27 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![npm](https://badge.fury.io/js/jsonpath-object-transform.png)](http://badge.fury.io/js/jsonpath-object-transform) -Pulls data from an object literal using JSONPath and generate a new objects based on a template. Each of the template's properties can pull a single property from the source data or an array of all results found by its JSONPath. When pulling an array of data you can also supply a subtemplate to transform each item in the array. +Pulls data from an object literal using JSONPath and generate a new object based on a template. Each of the template's properties can pull a single property from the source data or an array of all results found by its JSONPath. When pulling an array of data you can also supply a subtemplate to transform each item in the array. Both keys and values will be interpolated. JSONPath is like XPath for JavaScript objects. To learn the syntax, read the documentation for the [JSONPath](https://www.npmjs.org/package/JSONPath) package on npm and the [original article](http://goessner.net/articles/JsonPath/) by Stefan Goessner. @@ -115,7 +115,7 @@ Use an `Array` with a `String` and an `Object` to assign all results returned fr #### Example ```js var template = { - foo: [$..example, { + foo: ['$..example', { bar: '$.demo' }] }; @@ -142,3 +142,98 @@ Result: ] } ``` + +### Merge Items Returned in Array into a single object +```js +{ destination: ['$.path.to.sources', { '$.item.key': '$.item.path' }, {merge: true}] } +``` +Use an `Array` with a `String` and an `Object` to assign all results returned from your JSONPath and transform each of the objects with a subtemplate. The "merge: true" option will merge the results into one object. e.g. +```js +[{a:'b'},{c:'d'},{e:'f'}] +``` +-> +```js +{a:'b',c:'d',e:'f'} +``` + +#### Example +```js +var template = { + foo: ['$..example', { + '$.key': '$.demo' + }, {merge: true}] +}; + +var data = { + a: { + example: { + key: 'a', + demo: 'baz' + } + }, + b: { + // NOTE: you can use arrays or objects in this example and the previous one + example: [{ + key: 'b', + demo: 'qux' + },{ + key: 'c', + demo: 'max' + }] + } +}; +``` +Result: +```js +{ + foo: { + a: 'baz', + b: 'qux', + c: 'max' + } +} +``` + +### Dynamic Keys +```js +{ '$.aKey': ['$.path.to.sources', { '$.item.key': '$.item.path' }] } +``` +Use any valid JSON path as the key to assign a dynamic key to your result object + +#### Example +```js +var template = { + '$.a.example.demo': ['$..example', { + '$.key': '$.demo' + }, {merge: true}] +}; + +var data = { + a: { + example: { + key: 'a', + demo: 'baz' + } + }, + b: { + // NOTE: you can use arrays or objects in this example and the previous one + example: [{ + key: 'b', + demo: 'qux' + },{ + key: 'c', + demo: 'max' + }] + } +}; +``` +Result: +```js +{ + "baz": { + "a": "baz", + "b": "qux", + "c": "max" + } +} +``` From 88abaefb1dd70aab63414c1f7851756b8e386ba8 Mon Sep 17 00:00:00 2001 From: William Neely Date: Mon, 12 Feb 2018 17:26:26 -0500 Subject: [PATCH 05/11] revert change don't remember changing this code but showed up in the diff so putting it back. My tests still pass. --- lib/jsonpath-object-transform.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/jsonpath-object-transform.js b/lib/jsonpath-object-transform.js index 4ca194f..584f728 100644 --- a/lib/jsonpath-object-transform.js +++ b/lib/jsonpath-object-transform.js @@ -76,9 +76,12 @@ * @param {string} key */ function seekSingle(data, pathStr, result, key) { - var seek = jsonPath.eval(data, pathStr) || []; - - result[key] = seek.length ? seek[0] : undefined; + if(pathStr.indexOf('$') < 0){ + result[key] = pathStr; + } else { + var seek = jsonPath.eval(data, pathStr) || []; + result[key] = seek.length ? seek[0] : undefined; + } } /** From be31ab5b30b97a2cdd73a27ce7afe0e92eaee6ab Mon Sep 17 00:00:00 2001 From: William Neely Date: Sun, 29 Jul 2018 14:59:53 -0400 Subject: [PATCH 06/11] allow direct access to values. @example template = "$.bob" and element = {bob:123} --- lib/jsonpath-object-transform.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/jsonpath-object-transform.js b/lib/jsonpath-object-transform.js index 584f728..88c8caa 100644 --- a/lib/jsonpath-object-transform.js +++ b/lib/jsonpath-object-transform.js @@ -161,6 +161,14 @@ if (needsWrapper) { result = result.temp; } + /** + * allow direct access to values. + * @example template = "$.bob" and element = {bob:123} + * without this, we return {undefined: 123}, with this, we return 123 + */ + if (result['undefined']) { + result = result['undefined']; + } return result; }; From 73f7afe696c25776e16efd92ecb42109824b07d5 Mon Sep 17 00:00:00 2001 From: William Neely Date: Sun, 5 Aug 2018 22:27:13 -0400 Subject: [PATCH 07/11] make sure that undefined is an empty object --- lib/jsonpath-object-transform.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/jsonpath-object-transform.js b/lib/jsonpath-object-transform.js index 88c8caa..d55b039 100644 --- a/lib/jsonpath-object-transform.js +++ b/lib/jsonpath-object-transform.js @@ -161,13 +161,18 @@ if (needsWrapper) { result = result.temp; } + const undefResult = result['undefined']; /** * allow direct access to values. * @example template = "$.bob" and element = {bob:123} * without this, we return {undefined: 123}, with this, we return 123 + * the second if is for when nothing is found - if nothing is found, we would return {undefined:undefined} + * that's not helpful so we set it to an empty object */ - if (result['undefined']) { + if (undefResult) { result = result['undefined']; + } else if (undefResult === undefined && Object.hasOwnProperty(undefined)) { + result = {}; } return result; }; From 5ae5f4dec1077b07b7d4ca1e3010ae60ad06c8a3 Mon Sep 17 00:00:00 2001 From: William Neely Date: Mon, 6 Aug 2018 12:06:11 -0400 Subject: [PATCH 08/11] wasn't referencing the result object --- lib/jsonpath-object-transform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jsonpath-object-transform.js b/lib/jsonpath-object-transform.js index d55b039..886c7f2 100644 --- a/lib/jsonpath-object-transform.js +++ b/lib/jsonpath-object-transform.js @@ -171,7 +171,7 @@ */ if (undefResult) { result = result['undefined']; - } else if (undefResult === undefined && Object.hasOwnProperty(undefined)) { + } else if (undefResult === undefined && result.hasOwnProperty('undefined')) { result = {}; } return result; From 0afe6906a4e107d70f2503adfc98d4f4833a36e5 Mon Sep 17 00:00:00 2001 From: William Neely Date: Fri, 14 Sep 2018 13:44:29 -0400 Subject: [PATCH 09/11] if there's an undefined value, return it --- lib/jsonpath-object-transform.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/jsonpath-object-transform.js b/lib/jsonpath-object-transform.js index 886c7f2..3b53767 100644 --- a/lib/jsonpath-object-transform.js +++ b/lib/jsonpath-object-transform.js @@ -166,13 +166,10 @@ * allow direct access to values. * @example template = "$.bob" and element = {bob:123} * without this, we return {undefined: 123}, with this, we return 123 - * the second if is for when nothing is found - if nothing is found, we would return {undefined:undefined} - * that's not helpful so we set it to an empty object + * if nothing is found, this allows us to return undefined */ - if (undefResult) { - result = result['undefined']; - } else if (undefResult === undefined && result.hasOwnProperty('undefined')) { - result = {}; + if (result.hasOwnProperty('undefined')) { + result = undefResult; } return result; }; From 6c84fd54cf2d100f5ea1f782c394789a25235c7c Mon Sep 17 00:00:00 2001 From: William Neely Date: Wed, 13 Mar 2019 13:29:22 -0400 Subject: [PATCH 10/11] fix issue where numbers were removed from the resultant object --- lib/jsonpath-object-transform.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/jsonpath-object-transform.js b/lib/jsonpath-object-transform.js index 3b53767..896f5c2 100644 --- a/lib/jsonpath-object-transform.js +++ b/lib/jsonpath-object-transform.js @@ -50,6 +50,8 @@ case 'object': fn = seekObject; break; + case default: + result[key] = path; } if (fn) { From 2a1c4a845ee21b67f8128a63383ca257e301e948 Mon Sep 17 00:00:00 2001 From: William Neely Date: Wed, 13 Mar 2019 13:31:24 -0400 Subject: [PATCH 11/11] typo case default -> default --- lib/jsonpath-object-transform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jsonpath-object-transform.js b/lib/jsonpath-object-transform.js index 896f5c2..329af0b 100644 --- a/lib/jsonpath-object-transform.js +++ b/lib/jsonpath-object-transform.js @@ -50,7 +50,7 @@ case 'object': fn = seekObject; break; - case default: + default: result[key] = path; }