Skip to content

Latest commit

 

History

History
707 lines (643 loc) · 33.1 KB

README.md

File metadata and controls

707 lines (643 loc) · 33.1 KB

Js-JSON-Go banner

codefactor npmSize codecov downloads maintainability

Retrieves and constructs values from/into JSON objects. Js-JSON-Go is a lightweight library that offers the ability to query JSON objects to obtain data and use the same query format to build JSON objects. Moreover, the combined effort of getting and setting values on a conditional JSON path allows for translation from a value of one JSON object into another JSON object. Js-JSON-GO offers queries and nested queries with support for simple boolean logic, regular expressions and custom functions.

Try on RunKit (Node)

Installation

Use npm to install Js-JSON-Go.

npm install js-json-go

Usage

All main functions of this library are exported and can be used according to its JSDocs definition. Besides, two constructors are made available: (1) Json and (2) Map. Use the Json constructor for (multiple) operations on the same JSON Object/Array. Use the Map constructor to translate one JSON Object/Array into another JSON Object/Array.

const JG = require('js-json-go');
const json = new JG.Json(object, settings);
const map = new JG.Map(origin, destination, settings);

new JG.Map(origin, destination, settings)

Construct a new JS-JSON-Go Map to map the result of the origin object into the destination object. Customize it with settings that are used for all actions on the map.

map.transform(originPath, destinationPath, settings)

Transforms a single value from originPath into destination object at destinationPath. Use custom settings when desired.

Example:

const inputObject = {
    timestamp: '2011-10-05T14:48:00.000Z',
    scans: [
        { barcode: 'abc123', success: true, identifier: 'A' },
        { barcode: 'def456', success: false, identifier: 'B' },
        { barcode: 'ghi789', success: true, identifier: 'C' },
    ],
};
const JsonGo = new JG.Map(inputObject, []);
JsonGo.transform('scans[*:(scan)].barcode', '[:(scan)].serialNumber');
JsonGo.transform('scans[{$.success = true}:(scan)].identifier', '[:(scan)].identifier'); 
JsonGo.transform('timestamp', '[*].time', { formatter(value) { return new Date(value).getTime(); } }); 
const result = JsonGo.export();
/**
[
    { serialNumber: 'abc123', identifier: 'A', time: 1317826080000 },
    { serialNumber: 'def456', time: 1317826080000 },
    { serialNumber: 'ghi789', identifier: 'C', time: 1317826080000 },
]
 */

Example 2:

const inputObject = {
    timestamp: '2011-10-05T14:48:00.000Z',
    scans: [
        { barcode: 'abc123', success: true, identifier: 'A' },
        { barcode: 'def456', success: false, identifier: 'B' },
        { barcode: 'ghi789', success: true, identifier: 'C' },
    ],
};
const JsonGo = new JG.Map(inputObject, []);
JsonGo.transform('scans[*:(scan)].identifier', '[=(scans[:(scan)].barcode)].ID');
const result = JsonGo.export();
/**
{ 
    abc123: { ID: 'A' },
    def456: { ID: 'B' },
    ghi789: { ID: 'C' }
}
*/

map.set

Sets value on specified path onto destinationObject. Use custom settings when desired. Sets all elements that matches the path.

const originObject = null;
const destinationObject = [{}, {}, {}];
const JsonGo = new JG.Map(originObject, destinationObject);
JsonGo.set('[*].attributes[0].code', 8);
const result = JsonGo.export();
/**
[
    { attributes: [{ code: 8 }] }, 
    { attributes: [{ code: 8 }] }, 
    { attributes: [{ code: 8 }] }
]
*/

map.build

Similar to set, but build will set the returned output of functionToCall on specified path. Use custom settings when desired. Sets all elements that matches the path.

const originObject = null;
const destinationObject = [{}, {}, {}];
const JsonGo = new JG.Map(originObject, destinationObject);
const functionToCall = () => 8;
JsonGo.build('[*].attributes[0].code', functionToCall);
const result = JsonGo.export();
/**
[
    { attributes: [{ code: 8 }] }, 
    { attributes: [{ code: 8 }] }, 
    { attributes: [{ code: 8 }] }
]
*/

map.export

Returns the (modified) JSON destination object.

new JG.Json(object, settings)

Construct a new JS-JSON-Go Json to query or update a single JSON object, customize it with settings that are made available for all actions on that JSON object.

json.get(path, settings)

Retrieves values from objects specified path. Use custom settings when desired. Returns all elements that match the path.

const inputObject = {
    scans: [
        { barcode: 'abc123', success: true, identifier: 'A' },
        { barcode: 'def456', success: false, identifier: 'B' },
        { barcode: 'ghi789', success: true, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
const result = JsonGo.get('scans[*].barcode');
/**
[ 'abc123', 'def456' ]
 */
const result2 = JsonGo.get('scans[{$.success = true}].barcode');
/**
[ 'abc123', 'ghi789' ]
 */
const result3 = JsonGo.get('some.non.existing.path');
/**
[ ]
 */

json.getOne(path, settings)

Retrieves single value from objects specified path. This is overruling the limit setting and will always return a single result. Use custom settings when desired. Returns first element that matches the path.

const inputObject = {
    scans: [
        { barcode: 'abc123', success: true, identifier: 'A' },
        { barcode: 'def456', success: false, identifier: 'B' },
        { barcode: 'ghi789', success: true, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
const result = JsonGo.getOne('scans[*].barcode');
/**
'abc123'
 */
const result2 = JsonGo.getOne('scans[{$.success = false}].barcode');
/**
'def456'
 */
const result3 = JsonGo.getOne('some.non.existing.path');
/**
undefined
 */

json.getAll(path, settings)

Retrieves all values from objects specified path. This is overruling the limit setting and will always return a all results. Use custom settings when desired. Returns all elements that match the path.

const inputObject = {
    scans: [
        { barcode: 'abc123', success: true, identifier: 'A' },
        { barcode: 'def456', success: false, identifier: 'B' },
        { barcode: 'ghi789', success: true, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
const result = JsonGo.getAll('scans[*].barcode');
/**
[ 'abc123', 'def456', 'ghi789' ]
 */
const result2 = JsonGo.getAll('scans[{$.success = false}].barcode');
/**
[ 'def456' ]
 */
const result3 = JsonGo.getAll('some.non.existing.path');
/**
[ ]
 */

json.getPaths(path, settings)

Similar to json.get, but returns the resolved paths, rather than the values on these paths. Retrieves all resolved paths from objects specified input path. Use custom settings when desired. Returns all elements that match the input path.

const inputObject = {
    scans: [
        { barcode: 'abc123', success: true, identifier: 'A' },
        { barcode: 'def456', success: false, identifier: 'B' },
        { barcode: 'ghi789', success: true, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
const result = JsonGo.getPaths('scans[*].barcode');
/**
['scans[0].barcode', 'scans[1].barcode']
*/

json.getPath(path, settings)

Similar to json.getOne, but returns the resolved path, rather than the value on that path. Retrieves resolved path from objects specified input path. Use custom settings when desired. Returns first element that matches the input path.

const inputObject = {
    scans: [
        { barcode: 'abc123', success: true, identifier: 'A' },
        { barcode: 'def456', success: false, identifier: 'B' },
        { barcode: 'ghi789', success: true, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
const result = JsonGo.getPath('scans[*].barcode');
/**
'scans[0].barcode'
*/

json.getAllPaths(path, settings)

Similar to json.getAll, but returns the resolved paths, rather than the values on these paths. Retrieves all resolved paths from objects specified input path. Use custom settings when desired. Returns all elements that match the input path.

const inputObject = {
    scans: [
        { barcode: 'abc123', success: true, identifier: 'A' },
        { barcode: 'def456', success: false, identifier: 'B' },
        { barcode: 'ghi789', success: true, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
const result = JsonGo.getAllPaths('scans[*].barcode');
/**
['scans[0].barcode', 'scans[1].barcode', 'scans[2].barcode']
*/

json.find(path, settings)

Combining json.get and json.getPaths. Retrieves all resolved path and value from objects specified input path. Use custom settings when desired. Returns all elements that match the input path. Output is an array with objects containing resolved path and value properties.

const inputObject = {
    scans: [
        { barcode: 'abc123', success: true, identifier: 'A' },
        { barcode: 'def456', success: false, identifier: 'B' },
        { barcode: 'ghi789', success: true, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
const result = JsonGo.find('scans[*].barcode');
/**
[{ value: 'abc123', path: 'scans[0].barcode', references: {} }, 
 { value: 'def456', path: 'scans[1].barcode', references: {} }]
*/
const result2 = JsonGo.find('scans[*:(scan)].barcode');
/**
[{ value: 'abc123', path: 'scans[0].barcode', references: { scan: 0 } }, 
 { value: 'def456', path: 'scans[1].barcode', references: { scan: 1 } }]
*/

json.findOne(path, settings)

Combining json.getOne and json.getPaths. Retrieves resolved path and value from objects specified input path. Use custom settings when desired. Returns first element that matches the input path. Returns an object with resolved path and value properties.

const inputObject = {
    scans: [
        { barcode: 'abc123', success: true, identifier: 'A' },
        { barcode: 'def456', success: false, identifier: 'B' },
        { barcode: 'ghi789', success: true, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
const result = JsonGo.findOne('scans[*].barcode');
/**
{ value: 'abc123', path: 'scans[0].barcode', references: {} }
*/
const result2 = JsonGo.findOne('scans[*:(scan)].barcode');
/**
{ value: 'abc123', path: 'scans[0].barcode', references: { scan: 0 } }
*/

json.findAll(path, settings)

Combining json.getAll and json.getAllPaths. Retrieves resolved path and value from objects specified input path. Use custom settings when desired. Returns first element that matches the input path. Returns an object with resolved path and value properties.

const inputObject = {
    scans: [
        { barcode: 'abc123', success: true, identifier: 'A' },
        { barcode: 'def456', success: false, identifier: 'B' },
        { barcode: 'ghi789', success: true, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
const result = JsonGo.findAll('scans[*].barcode');
/**
[{ value: 'abc123', path: 'scans[0].barcode', references: {} }, 
 { value: 'def456', path: 'scans[1].barcode', references: {} }, 
 { value: 'ghi789', path: 'scans[2].barcode', references: {} }]
*/
const result2 = JsonGo.findAll('scans[*:(scan)].barcode');
/**
[{ value: 'abc123', path: 'scans[0].barcode', references: { scan: 0 } }, 
 { value: 'def456', path: 'scans[1].barcode', references: { scan: 1 } }, 
 { value: 'ghi789', path: 'scans[2].barcode', references: { scan: 2 } }]
*/

json.set(path, value, settings)

Sets value on specified path. Use custom settings when desired. Sets all elements that matches the path.

const inputObject = {
    scans: [
        { barcode: 'abc123', accuracy: 90, identifier: 'A' },
        { barcode: 'def456', accuracy: 50, identifier: 'B' },
        { barcode: 'ghi789', accuracy: 94, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
JsonGo.set('scans[*].attributes[0].code', 8);
const result = JsonGo.export();
/**
{
    scans: [
        { barcode: 'abc123', accuracy: 90, identifier: 'A', attributes: [{ code: 8 }] },
        { barcode: 'def456', accuracy: 50, identifier: 'B', attributes: [{ code: 8 }] },
        { barcode: 'ghi789', accuracy: 94, identifier: 'C' },
    ],
}
*/

json.setOne(path, value, settings)

Sets single value on specified path. Use custom settings when desired. Sets the first element that matches the path.

const inputObject = {
    scans: [
        { barcode: 'abc123', accuracy: 90, identifier: 'A' },
        { barcode: 'def456', accuracy: 50, identifier: 'B' },
        { barcode: 'ghi789', accuracy: 94, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
JsonGo.setOne('scans[{$.accuracy >= 90}].success', true);
const result = JsonGo.export();
/**
{
    scans: [
        { barcode: 'abc123', accuracy: 90, identifier: 'A', success: true },
        { barcode: 'def456', accuracy: 50, identifier: 'B' },
        { barcode: 'ghi789', accuracy: 94, identifier: 'C' },
    ],
}
*/

json.setAll(path, value, settings)

Sets all values on specified path. Use custom settings when desired. Sets the first element that matches the path.

const inputObject = {
    scans: [
        { barcode: 'abc123', accuracy: 90, identifier: 'A' },
        { barcode: 'def456', accuracy: 50, identifier: 'B' },
        { barcode: 'ghi789', accuracy: 94, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
JsonGo.setAll('scans[{$.accuracy > 30}].accuracy', 99);
const result = JsonGo.export();
/**
{
    scans: [
        { barcode: 'abc123', accuracy: 99, identifier: 'A' },
        { barcode: 'def456', accuracy: 99, identifier: 'B' },
        { barcode: 'ghi789', accuracy: 99, identifier: 'C' },
    ],
}
*/

json.build(path, value, settings)

Similar to set, but build will set the returned output of functionToCall on specified path. Use custom settings when desired. Sets all elements that matches the path.

const inputObject = {
    scans: [
        { barcode: 'abc123', accuracy: 90, identifier: 'A' },
        { barcode: 'def456', accuracy: 50, identifier: 'B' },
        { barcode: 'ghi789', accuracy: 94, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
JsonGo.build('scans[*].attributes[0].code', () => 8);
const result = JsonGo.export();
/**
{
    scans: [
        { barcode: 'abc123', accuracy: 90, identifier: 'A', attributes: [{ code: 8 }] },
        { barcode: 'def456', accuracy: 50, identifier: 'B', attributes: [{ code: 8 }] },
        { barcode: 'ghi789', accuracy: 94, identifier: 'C' },
    ],
}
*/

json.buildOne(path, value, settings)

Similar to setOne, but build will set the returned output of functionToCall on specified path. Use custom settings when desired. Sets the first element that matches the path.

const inputObject = {
    scans: [
        { barcode: 'abc123', accuracy: 90, identifier: 'A' },
        { barcode: 'def456', accuracy: 50, identifier: 'B' },
        { barcode: 'ghi789', accuracy: 94, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
JsonGo.setOne('scans[{$.accuracy >= 90}].success', () => true);
const result = JsonGo.export();
/**
{
    scans: [
        { barcode: 'abc123', accuracy: 90, identifier: 'A', success: true },
        { barcode: 'def456', accuracy: 50, identifier: 'B' },
        { barcode: 'ghi789', accuracy: 94, identifier: 'C' },
    ],
}
*/

json.buildAll(path, value, settings)

Similar to setAll, but build will set the returned output of functionToCall on specified path. Use custom settings when desired. Sets the first element that matches the path.

const inputObject = {
    scans: [
        { barcode: 'abc123', accuracy: 90, identifier: 'A' },
        { barcode: 'def456', accuracy: 50, identifier: 'B' },
        { barcode: 'ghi789', accuracy: 94, identifier: 'C' },
    ],
};
const JsonGo = new JG.Json(inputObject, { limit: 2 });
JsonGo.buildAll('scans[{$.accuracy > 30}].accuracy', () => 99);
const result = JsonGo.export();
/**
{
    scans: [
        { barcode: 'abc123', accuracy: 99, identifier: 'A' },
        { barcode: 'def456', accuracy: 99, identifier: 'B' },
        { barcode: 'ghi789', accuracy: 99, identifier: 'C' },
    ],
}
*/

json.chop(chopSize)

Chops an array or object into smaller pieces with a maximum size of chopSize.

const barcodes = ['001', '002', '003', '004', '005', '006', '007', '008', '009', '010'];
const JsonGo = new JG.Json(barcodes);
const result = JsonGo.chop(3);
/**
[
    ['001', '002', '003'],
    ['004', '005', '006'],
    ['007', '008', '009'],
    ['010'],
]
*/

json.export

Returns the (modified) JSON object.

settings for Json and Map constructor

The following settings can be passed into the settings object:

  • unlinkInputObject: if set to true, the origin object will not be altered by any of the operations, default value is false.
  • mapIfNotFound: if set to true the query result will always be mapped, even if the query did not return any matches, default value is false.
  • ignoreOnTransform: array of responses from originObject that should not be translated within Map constructors translate functions into destinationObject. Default is [].
  • limit: maximum number of values that should be resolved. Default is 0 (returning all values that match input path).
  • formatter: this function is called before returning get/find result. Input of the function is the resulting value of get/find. Output of the formatter function will be returned instead of the original value. Formatter will also be called on transform. Default is: (value) => value.
  • functions: object with functions that can be called from within query path. KeyName can be called with $Function(keyName) from query path. Default is: {}.
  • parse: if set to true each queried element that is not yet of type object will be attempted to parse. By doing so there is no need to deep parse the input object before querying. This setting has no effect while building JSON paths. Default is: false

Js-JSON-Go Path Syntax

Js-JSON-Go refers to a JSON-structure in a similar manner as the bracket and/or dot notation in JavaScript. In principle applies that a dot-notated child refers to a child within an object, and a bracket-notated child to either an object or an array. Moreover, with bracket notation Js-JSON-Go allows to query over all children/elements at the regarding depth. Querying is not limited to its regarding depth, meaning it is allowed to query both parents and children, but also parents and children that contain their own query.

The following syntax can be used (note that this table is reflecting priority, meaning that the upper syntax is dominant over lower syntax):

Element Syntax Description
[element] Bracket-notated child
.element Dot-notated child
["element"] Element is considered a single string
['element'] Element is considered a single string
[{element}] Element is considered a query
[xxx:(ref)] ref is considered a reference that can be reused
[=(some.path.at.origin)] some.path.at.origin will be resolved from origin object
Custom Syntax Description
[*] or [{*}] or [{$all}] Wildcard, relates to existing, but unknown element(s)
[{$end}] Refers to last element in array
[{$append}] May be used on set to indicate a new element in array

A query is considered a logical test of two paths or elements separated by an operator, or a single path / element which will then be tested as !falsy. $Function() Queries are considered special. Instead of defining a logical test within the query, a function refers to a functionName. The corresponding function (passed in the functions object of the Json/Map constructor or regarding query (get, set, translate)) is then expected to return a boolean response as result of a custom logical test.

Query Element Syntax Description
null null not surrounded by quotes is considered null
undefined undefined not surrounded by quotes is considered undefined
true true not surrounded by quotes is considered true
false false not surrounded by quotes is considered false
number a number not surrounded by quotes is considered to be of type number
"string" Element is considered a to be a string
'string' Element is considered a to be a string
$path Element relates to JSON location. $ followed by a path is considered to have its origin at the root of the object
$.path Element relates to JSON location. $. followed by a path is considered to have its origin at the current depth within object
$..path Element relates to JSON location. $.. followed by a path is considered to have its origin at the parent of the current depth within object (each additional dot relates to the parent of the corresponding element)
$JSON(stringified JSON) Element is considered to be a JSON.
$RegExp(regular expression) Element is considered to be a Regular Expression (be aware some chars need to be escaped using \).
$Function(functionName). Element is considered to be a function (function name needs to match key of input function object).
Query Operators Description
= validates whether first element strict equals second element
!= validates whether first element does not strict equal second element
>= validates whether first element greater than or equal to second element
> validates whether first element greater than second element
<= validates whether first element smaller than or equal to second element
< validates whether first element smaller than second element
or @ validates whether first element is in subset of second element
or !@ validates whether first element is in subset of second element
? test element against regular expression

Use of custom functions

Functions can be passed as additional input in the Json or Map constructor, and/or as input for the specific query on get/set/translate. The input is expected to be an object with functions. The keyName of the function can be called from the query by $Function(keyName). The function is called for each element at the corresponding depth. Input for the function is the element that is found, the output is expected to be a boolean, return true if the query should be considered a match, or false otherwise.

Examples

Usage will be demonstrated with an example "inputFixture" json file:

{
    "holding": "appleCompany",
    "mainStore": "Amsterdam",
    "stores": [
        {
            "storeName": "Berlin",
            "expensive": 5,
            "items": [
                {
                    "name": "Granny Smith small bag",
                    "price": 3
                },
                {
                    "name": "Granny Smith medium bag",
                    "price": 5
                },
                {
                    "name": "Granny Smith large bag",
                    "price": 6
                },
                {
                    "name": "Fuji small bag",
                    "price": 3.50
                },
                {
                    "name": "Pink Lady small bag",
                    "price": 8
                },
                {
                    "name": "Pink Lady medium bag",
                    "price": 8
                }
            ]
        },
        {
            "storeName": "Amsterdam",
            "expensive": 6,
            "items": [
                {
                    "name": "Pink Lady medium bag",
                    "price": 8,
                    "expensive": true
                },
                {
                    "name": "Pink Lady large bag",
                    "price": 10,
                    "expensive": true
                }
            ]
        },
        {
            "storeName": "Rome",
            "expensive": null,
            "items": []
        }
    ]
}
const JG = require('js-json-go');
const inputFixture = require('./fixtures/inputFixture.json');

/*
-- use of getOne will return a single output (first result that complies with input query)
*/
const JsonGo = new JG.Json(inputFixture);
const result1 = JsonGo.getOne('stores[0].storeName'); //'Berlin' -> get storeName from stores with element 0 
const result2 = JsonGo.getOne('stores["0"].storeName'); //undefined -> get storeName from stores with element '0', undefined because stores is not an object wity key '0'
const result3 = JsonGo.getOne('stores[{$end}].storeName'); //'Rome' -> get storeName of last store from stores
const result4 = JsonGo.getOne('stores[{$.storeName = $mainStore}].expensive'); //6 -> get expensive field of store where storeName equals mainStore (derived from origin of object)
const result5 = JsonGo.getOne('stores[{$.storeName = $..mainStore}].expensive'); //6 -> get expensive field of store where storeName equals mainStore (derived from relative location)
const result6 = JsonGo.getOne('stores[{$.items[{$.name = "Pink Lady large bag"}]}].storeName'); //'Amsterdam' -> get storeName of store that has an item with name Pink Lady large bag
const result7 = JsonGo.getOne('stores[{$.items[{$.expensive = true}]}].storeName'); //'Amsterdam' -> get storeName of store that has items where expensive = true (boolean)
const result8 = JsonGo.getOne('stores[{$.items[{$.expensive = "true"}]}].storeName'); //undefined -> get storeName of store that has items where expensive = 'true' (string)


/*
-- use of get will return all things that comply to the query
*/
const JsonGo = new JG.Json(inputFixture);
const result1 = JsonGo.get('stores[0].storeName'); //['Berlin'] -> get storeName from stores with element 0 
const result2 = JsonGo.get('stores["0"].storeName'); //[] -> get storeName from stores with element '0', undefined because stores is not an object wity key '0'
const result3 = JsonGo.get('stores[{$.items[{$.name = "Pink Lady large bag"}]}].storeName'); //['Amsterdam'] -> get storeName of store that has an item with name Pink Lady large bag
const result4 = JsonGo.get('stores[{$.items[{Pink ∈ $.name}]}].storeName'); //['Berlin', 'Amsterdam'] -> get storeName of store that has an item with Pink in its name
const result5 = JsonGo.get('stores[{$.items[{Fuji ∈ $.name}]}].items[{medium ∉ $.name}].name'); //["Granny Smith small bag", "Granny Smith large bag", "Fuji small bag", "Pink Lady small bag"] -> get al item names that do not have 'medium' in its name from stores that have items with 'Fuji' in its name.
const result6 = JsonGo.get(`stores[{$.storeName ∈ $JSON(${JSON.stringify(['Berlin', 'Barcelona'])})}].storeName`); //['Berlin'] -> get storeNames of store that has storename in ['Berlin', 'Barcelona']
const result7 = JsonGo.get('stores[{$.storeName ? $RegExp(/.*AMS.*/i)}].storeName'); //['Amsterdam'] -> get storeNames containing case insensitive AMS in its storeName using a regular expression
const functions = {
    customFunction: (element) => {
        if(['Amsterdam', 'Rome'].indexOf(element.storeName) > -1){
            return true;
        }
        return false;
    }
};
const result8 = JsonGo.get('stores[{$Function(customFunction)}].storeName', functions); //['Amsterdam', 'Rome'] -> get storeNames for all elements where customFunction(element) returns true


/*
-- set and setAll will set values on the specified path, using these functions the inputFixture can be generated with the code below
*/
const JsonGo = new JG.Json({});
JsonGo.setOne('holding', 'appleCompany');
JsonGo.setOne('mainStore', 'Amsterdam');
JsonGo.setOne('stores[{$append}].storeName', 'Berlin');
JsonGo.setOne('stores[{$end}].expensive', 5);
JsonGo.setOne('stores[{$end}].items[{$append}].name', 'Granny Smith small bag');
JsonGo.setOne('stores[{$end}].items[{$end}].price', 3);
JsonGo.setOne('stores[{$end}].items[{$append}].name', 'Granny Smith medium bag');
JsonGo.setOne('stores[{$end}].items[{$end}].price', 5);
JsonGo.setOne('stores[{$end}].items[{$append}].name', 'Granny Smith large bag');
JsonGo.setOne('stores[{$end}].items[{$end}].price', 6);
JsonGo.setOne('stores[{$end}].items[{$append}].name', 'Fuji small bag');
JsonGo.setOne('stores[{$end}].items[{$end}].price', 3.50);
JsonGo.setOne('stores[{$end}].items[{$append}].name', 'Pink Lady small bag');
JsonGo.setOne('stores[{$end}].items[{$append}].name', 'Pink Lady medium bag');
JsonGo.setOne('stores[{$append}].storeName', 'Amsterdam');
JsonGo.setOne('stores[{$end}].expensive', 6);
JsonGo.setOne('stores[{$end}].items[{$append}].name', 'Pink Lady medium bag');
JsonGo.setOne('stores[{$end}].items[{$append}].name', 'Pink Lady large bag');
JsonGo.setOne('stores[{$end}].items[{$end}].price', 10);
JsonGo.setOne('stores[{$append}].storeName', 'Rome');
JsonGo.setOne('stores[{$end}].expensive', null);
JsonGo.setOne('stores[{$end}].items', []);
JsonGo.set(`stores[{1 = 1}].items[{$.name ∈ $JSON(${JSON.stringify(['Pink Lady medium bag', 'Pink Lady small bag'])})}].price`, 8); //sets price = 8 for all items in all stores where items name is in ['Pink Lady medium bag', 'Pink Lady small bag']
JsonGo.set('stores[{$.storeName = $mainStore}].items[{$.price >= $stores[{$.storeName = $mainStore}].expensive}].expensive', true); // sets expensive key/value where price >= expensive field of mainStore
const result = JsonGo.export();

Performance

The querying performance of this library is tested with some simple queries on 3 datasets of different sizes using the "json-querying-performance-testing" library of andykais (https://github.com/andykais/json-querying-performance-testing) and compared to different libraries that also facilitate JSON querying. Js-JSON-Go is outperforming most of them on both small and large datasets.

Testing

Tests can be ran using the following command:

npm run test

Current code coverage 100%.

Contributing

Pull requests are welcome. Please make sure to update tests as appropriate.

License

MIT