Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions dist/index-es.js
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ JSONPath.prototype.evaluate = function (expr, json, callback, otherTypeCallback)
return wrap ? [] : undefined;
}

if (result.length === 1 && !wrap && !Array.isArray(result[0].value)) {
if (!wrap && this.isSingularResult(result, exprList)) {
return this._getPreferredOutput(result[0]);
}

Expand Down Expand Up @@ -522,6 +522,52 @@ JSONPath.prototype._getPreferredOutput = function (ea) {
return JSONPath.toPointer(ea.path);
}
};
/**
* Detect filter expressions.
* @param {string}loc
* @returns {boolean}
*/


JSONPath.prototype.isFilterExpr = function (loc) {
return loc.indexOf('?(') === 0;
};
/**
* Detects operators in the expression list that require an array result.
* an array of results. If no such operator exists, the result
* will be treated as a singular value.
*
* For example, the following paths reference singular results:
* "store.book[0]" - specific book
* "store.bicycle.red" - single property of a single object
*
* Conversely, the following paths will always result in an array,
* because they can generate multiple results depending on the dataset:
* $.store.book[0][category,author] - category,author will return 2 values
* $..book - ".." will recurse through the store object
* $.store.book[1:2] - indicates a range within the array
* $.store.book[*] - wild card indicates multiple results
* $.store.book[?(@.isbn)] - filtering
*/

/**
* @param {PlainObject} result - json path result
* @param {array} exprList - array of json path expressions
* @returns {boolean}
*/


JSONPath.prototype.isSingularResult = function (result, exprList) {
var _this2 = this;

return result.length === 1 && !exprList.includes('*') && !exprList.includes('..') && exprList.every(function (loc) {
return !_this2.isFilterExpr(loc);
}) && exprList.every(function (loc) {
return !loc.includes(',');
}) && exprList.every(function (loc) {
return !loc.includes(':');
});
};

JSONPath.prototype._handleCallback = function (fullRetObj, callback, type) {
if (callback) {
Expand Down Expand Up @@ -638,7 +684,7 @@ JSONPath.prototype._trace = function (expr, val, path, parent, parentPropName, c
} else if (/^(\x2D?[0-9]*):(\x2D?[0-9]*):?([0-9]*)$/.test(loc)) {
// [start:end:step] Python slice syntax
addRet(this._slice(loc, x, val, path, parent, parentPropName, callback));
} else if (loc.indexOf('?(') === 0) {
} else if (this.isFilterExpr(loc)) {
// [?(expr)] (filtering)
if (this.currPreventEval) {
throw new Error('Eval [?(expr)] prevented in JSONPath expression.');
Expand Down
2 changes: 1 addition & 1 deletion dist/index-es.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index-es.min.js.map

Large diffs are not rendered by default.

50 changes: 48 additions & 2 deletions dist/index-umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@
return wrap ? [] : undefined;
}

if (result.length === 1 && !wrap && !Array.isArray(result[0].value)) {
if (!wrap && this.isSingularResult(result, exprList)) {
return this._getPreferredOutput(result[0]);
}

Expand Down Expand Up @@ -528,6 +528,52 @@
return JSONPath.toPointer(ea.path);
}
};
/**
* Detect filter expressions.
* @param {string}loc
* @returns {boolean}
*/


JSONPath.prototype.isFilterExpr = function (loc) {
return loc.indexOf('?(') === 0;
};
/**
* Detects operators in the expression list that require an array result.
* an array of results. If no such operator exists, the result
* will be treated as a singular value.
*
* For example, the following paths reference singular results:
* "store.book[0]" - specific book
* "store.bicycle.red" - single property of a single object
*
* Conversely, the following paths will always result in an array,
* because they can generate multiple results depending on the dataset:
* $.store.book[0][category,author] - category,author will return 2 values
* $..book - ".." will recurse through the store object
* $.store.book[1:2] - indicates a range within the array
* $.store.book[*] - wild card indicates multiple results
* $.store.book[?(@.isbn)] - filtering
*/

/**
* @param {PlainObject} result - json path result
* @param {array} exprList - array of json path expressions
* @returns {boolean}
*/


JSONPath.prototype.isSingularResult = function (result, exprList) {
var _this2 = this;

return result.length === 1 && !exprList.includes('*') && !exprList.includes('..') && exprList.every(function (loc) {
return !_this2.isFilterExpr(loc);
}) && exprList.every(function (loc) {
return !loc.includes(',');
}) && exprList.every(function (loc) {
return !loc.includes(':');
});
};

JSONPath.prototype._handleCallback = function (fullRetObj, callback, type) {
if (callback) {
Expand Down Expand Up @@ -644,7 +690,7 @@
} else if (/^(\x2D?[0-9]*):(\x2D?[0-9]*):?([0-9]*)$/.test(loc)) {
// [start:end:step] Python slice syntax
addRet(this._slice(loc, x, val, path, parent, parentPropName, callback));
} else if (loc.indexOf('?(') === 0) {
} else if (this.isFilterExpr(loc)) {
// [?(expr)] (filtering)
if (this.currPreventEval) {
throw new Error('Eval [?(expr)] prevented in JSONPath expression.');
Expand Down
2 changes: 1 addition & 1 deletion dist/index-umd.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index-umd.min.js.map

Large diffs are not rendered by default.

44 changes: 41 additions & 3 deletions src/jsonpath.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ JSONPath.prototype.evaluate = function (
.filter(function (ea) { return ea && !ea.isParentSelector; });

if (!result.length) { return wrap ? [] : undefined; }
if (result.length === 1 && !wrap && !Array.isArray(result[0].value)) {
if (!wrap && this.isSingularResult(result, exprList)) {
return this._getPreferredOutput(result[0]);
}
return result.reduce(function (rslt, ea) {
Expand Down Expand Up @@ -363,6 +363,45 @@ JSONPath.prototype._getPreferredOutput = function (ea) {
return JSONPath.toPointer(ea.path);
}
};
/**
* Detect filter expressions.
* @param {string}loc
* @returns {boolean}
*/
JSONPath.prototype.isFilterExpr = function (loc) {
return loc.indexOf('?(') === 0;
};
/**
* Detects operators in the expression list that require an array result.
* an array of results. If no such operator exists, the result
* will be treated as a singular value.
*
* For example, the following paths return whatever is found at the specified
* location, whether that is a scalar, object, or array:
* "store.book[0]" - specific book
* "store.bicycle.red" - single property of a single object
*
* Conversely, the following paths will always result in an array,
* because they can generate multiple results depending on the dataset:
* $.store.book[0][category,author] - category,author will return 2 values
* $..book - ".." will recurse through the store object
* $.store.book[1:2] - indicates a range within the array
* $.store.book[*] - wild card indicates multiple results
* $.store.book[?(@.isbn)] - filtering
*/
/**
* @param {PlainObject} result - json path result
* @param {array} exprList - array of json path expressions
* @returns {boolean}
*/
JSONPath.prototype.isSingularResult = function (result, exprList) {
return (result.length === 1 &&
!exprList.includes('*') &&
!exprList.includes('..') &&
exprList.every((loc) => !this.isFilterExpr(loc)) &&
exprList.every((loc) => !loc.includes(',')) &&
exprList.every((loc) => !loc.includes(':')));
};

JSONPath.prototype._handleCallback = function (fullRetObj, callback, type) {
if (callback) {
Expand Down Expand Up @@ -421,7 +460,6 @@ JSONPath.prototype._trace = function (
ret.push(elems);
}
}

if ((typeof loc !== 'string' || literalPriority) && val &&
hasOwnProp.call(val, loc)
) { // simple case--directly follow property
Expand Down Expand Up @@ -479,7 +517,7 @@ JSONPath.prototype._trace = function (
addRet(
this._slice(loc, x, val, path, parent, parentPropName, callback)
);
} else if (loc.indexOf('?(') === 0) { // [?(expr)] (filtering)
} else if (this.isFilterExpr(loc)) { // [?(expr)] (filtering)
if (this.currPreventEval) {
throw new Error('Eval [?(expr)] prevented in JSONPath expression.');
}
Expand Down
26 changes: 26 additions & 0 deletions test/test.arr.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,30 @@ describe('JSONPath - Array', function () {
const result = jsonpath({json, path: 'store.books', flatten: true, wrap: false});
assert.deepEqual(expected, result);
});

it('query single element arr w/scalar value', () => {
const expected = [json.store.books[0].author];
const result = jsonpath({json, path: 'store.books[*].author', wrap: false});
assert.deepEqual(expected, result);
});

it('query single element arr w/array value', () => {
const authors = ['Dickens', 'Lancaster'];
const input = {
books: [{authors}]
};
const expected = authors;
const result = jsonpath({json: input, path: '$.books[0].authors', wrap: false});
assert.deepEqual(expected, result);
});

it('query multi element arr w/array value', () => {
const authors = ['Dickens', 'Lancaster'];
const input = {
books: [{authors}, {authors}]
};
const expected = [authors, authors];
const result = jsonpath({json: input, path: '$.books[*].authors', wrap: false});
assert.deepEqual(expected, result);
});
});
8 changes: 4 additions & 4 deletions test/test.eval.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('JSONPath - Eval', function () {
};

it('multi statement eval', () => {
const expected = json.store.books[0];
const expected = [json.store.books[0]];
const selector = '$..[?(' +
'var sum = @.price && @.price[0]+@.price[1];' +
'sum > 20;)]';
Expand All @@ -34,13 +34,13 @@ describe('JSONPath - Eval', function () {
});

it('accessing current path', () => {
const expected = json.store.books[1];
const expected = [json.store.books[1]];
const result = jsonpath({json, path: "$..[?(@path==\"$['store']['books'][1]\")]", wrap: false});
assert.deepEqual(expected, result);
});

it('sandbox', () => {
const expected = json.store.book;
const expected = [json.store.book];
const result = jsonpath({
json,
sandbox: {category: 'reference'},
Expand All @@ -50,7 +50,7 @@ describe('JSONPath - Eval', function () {
});

it('sandbox (with parsing function)', () => {
const expected = json.store.book;
const expected = [json.store.book];
const result = jsonpath({
json,
sandbox: {
Expand Down
19 changes: 19 additions & 0 deletions test/test.examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ describe('JSONPath - Examples', function () {
assert.deepEqual(expected, result);
});

it('range of property of entire tree w/ single element result', () => {
const book = json.store.book[0];
const input = {books: [book]};
const expected = [book];
let result = jsonpath({json: input, path: '$.books[0,1]', wrap: false});
assert.deepEqual(expected, result);

result = jsonpath({json: input, path: '$.books[:1]', wrap: false});
assert.deepEqual(expected, result);
});

it('categories and authors of all books', () => {
const expected = ['reference', 'Nigel Rees'];
const result = jsonpath({json, path: '$..book[0][category,author]'});
Expand All @@ -106,6 +117,14 @@ describe('JSONPath - Examples', function () {
assert.deepEqual(expected, result);
});

it('filter all properties if sub property exists, of single element array', () => {
const book = json.store.book[3];
const input = {books: [book]};
const expected = [book];
const result = jsonpath({json: input, path: '$.books[?(@.isbn)]', wrap: false});
assert.deepEqual(expected, result);
});

it('filter all properties if sub property greater than of entire tree', () => {
const books = json.store.book;
const expected = [books[0], books[2]];
Expand Down
8 changes: 8 additions & 0 deletions test/test.intermixed.arr.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,12 @@ describe('JSONPath - Intermixed Array', function () {
const result = jsonpath({json, path: '$.store..price', flatten: true});
assert.deepEqual(expected, result);
});

it('all sub properties of single element arr', () => {
const book = json.store.book[0];
const input = {book};
const expected = [book.title];
const result = jsonpath({json: input, path: '$..title', flatten: true, wrap: false});
assert.deepEqual(expected, result);
});
});
4 changes: 2 additions & 2 deletions test/test.properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ describe('JSONPath - Properties', function () {

it('At signs within properties', () => {
let result = jsonpath({json, path: "$.datafield[?(@.tag=='035')]", wrap: false});
assert.deepEqual(json.datafield[0], result);
assert.deepEqual([json.datafield[0]], result);
result = jsonpath({json, path: "$.datafield[?(@['@tag']=='042')]", wrap: false});
assert.deepEqual(json.datafield[1], result);
assert.deepEqual([json.datafield[1]], result);
result = jsonpath({json, path: "$.datafield[2][(@['@tag'])]", wrap: false});
assert.deepEqual(json.datafield[2]['045'], result);
});
Expand Down