diff --git a/CHANGES.md b/CHANGES.md index 3457fa28e0fa6cb76ca37be167e2965b04ded640..afcaa98b8f79543cdc847dfb6113ec64ff12e091 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,6 @@ +## Dec 9, 2014 +* Offer new class-based API and object-based arguments +* Version 0.11 ## Oct 23, 2013 diff --git a/README.md b/README.md index 5b8c779f0f5e577e8ee97fd3f7dc25077255234e..65dab4c5fcdeda2df7000f72a96c872a8e3a03d2 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,18 @@ -JSONPath [](http://travis-ci.org/s3u/JSONPath) -======== +# JSONPath [](http://travis-ci.org/s3u/JSONPath) Analyse, transform, and selectively extract data from JSON documents (and JavaScript objects). -Install -------- +# Install npm install JSONPath -Usage ------ +# Usage In node.js: ```js -var jsonPath = require('JSONPath'); -jsonPath.eval(obj, path); +var JSONPath = require('JSONPath'); +JSONPath({json: obj, path: path}); ``` For browser usage you can directly include `lib/jsonpath.js`, no browserify @@ -24,11 +21,32 @@ magic necessary: ```html <script src="lib/jsonpath.js"></script> <script> - jsonPath.eval(obj, path); + JSONPath({json: obj, path: path}); </script> ``` -Examples +An alternative syntax is available as: + +```js +JSONPath(options, obj, path); +``` + +The following format is now deprecated: + +```js +jsonPath.eval(options, obj, path); +``` + +Other properties that can be supplied for +options (the first argument) include: + +- ***autostart*** (**default: true**) - If this is supplied as `false`, one may call the `evaluate` method manually as needed. +- ***flatten*** (**default: false**) - Whether the returned array of results will be flattened to a single dimension array. +- ***resultType*** (**default: "value"**) - Can be case-insensitive form of "value" or "path" to determine whether to return results as the values of the found items or as their absolute paths. +- ***sandbox*** (**default: An empty object **) - Key-value map of variables to be available to code evaluations such as filtering expressions. (Note that the current path and value will also be available; see the Syntax section for details.) +- ***wrap*** (**default: true**) - Whether or not to wrap the results in an array. If `wrap` is set to false, and no results are found, `false` will be returned (as opposed to an empty array). If `wrap` is set to false and a single result is found, that result will be the only item returned. An array will still be returned if multiple results are found, however. + +Syntax with examples -------- Given the following JSON, taken from http://goessner.net/articles/JsonPath/ : @@ -73,24 +91,27 @@ Given the following JSON, taken from http://goessner.net/articles/JsonPath/ : ``` -XPath | JSONPath | Result -------------------- | ---------------------- | ------------------------------------- -/store/book/author | $.store.book[*].author | the authors of all books in the store -//author | $..author | all authors -/store/* | $.store.* | all things in store, which are some books and a red bicycle. -/store//price | $.store..price | the price of everything in the store. -//book[3] | $..book[2] | the third book -//book[last()] | $..book[(@.length-1)] | the last book in order. - | $..book[-1:] | -//book[position()<3]| $..book[0,1] | the first two books - | $..book[:2] | -//book[isbn] | $..book[?(@.isbn)] | filter all books with isbn number -//book[price<10] | $..book[?(@.price<10)] | filter all books cheapier than 10 -//*[price>19]/.. | $..[?(@.price>19)]^ | categories with things more expensive than 19 -//* | $..* | all Elements in XML document. All members of JSON structure. - -Development ------------ +XPath | JSONPath | Result | Notes +------------------- | ---------------------- | ------------------------------------- | ----- +/store/book/author | $.store.book[*].author | the authors of all books in the store | +//author | $..author | all authors | +/store/* | $.store.* | all things in store, which are some books and a red bicycle.| +/store//price | $.store..price | the price of everything in the store. | +//book[3] | $..book[2] | the third book | +//book[last()] | $..book[(@.length-1)]<br>$..book[-1:] | the last book in order.| +//book[position()<3]| $..book[0,1]<br>$..book[:2]| the first two books | +//book/*[self::category\|self::author] or //book/(category,author) in XPath 2.0| $..book[category,author]| the categories and authors of all books | +//book[isbn] | $..book[?(@.isbn)] | filter all books with isbn number | +//book[price<10] | $..book[?(@.price<10)] | filter all books cheapier than 10 | +//*[price>19]/.. | $..[?(@.price>19)]^ | categories with things more expensive than 19 | Parent (caret) not present in original spec +//* | $..* | all Elements in XML document. All members of JSON structure. | +/store/book/[position()!=1] | $.store.book[?(@path !== "$[\'store\'][\'book\'][0]")] | All books besides that at the path pointing to the first | @path not present in original spec + +Any additional variables supplied as properties on the optional +"sandbox" object option are also available to (parenthetical-based) +evaluations. + +# Development Running the tests on node: `npm test`. For in-browser tests: @@ -105,7 +126,6 @@ Running the tests on node: `npm test`. For in-browser tests: * To run the tests visit [http://localhost:8082/test/test.html](). -License -------- +# License [MIT License](http://www.opensource.org/licenses/mit-license.php). diff --git a/lib/jsonpath.js b/lib/jsonpath.js index 79de8690f470d1453368df9396a8faa71fc3e524..28e07371bd3d46c35303321f2eb80d80f70acbaa 100644 --- a/lib/jsonpath.js +++ b/lib/jsonpath.js @@ -33,154 +33,50 @@ var cache = {}; function push (arr, elem) {arr = arr.slice(); arr.push(elem); return arr;} function unshift (elem, arr) {arr = arr.slice(); arr.unshift(elem); return arr;} -function jsonPath (obj, expr, arg) { - var $ = obj; - var P = { - resultType: (arg && arg.resultType) || 'VALUE', - flatten: (arg && arg.flatten) || false, - wrap: (arg && arg.hasOwnProperty('wrap')) ? arg.wrap : true, - sandbox: (arg && arg.sandbox) ? arg.sandbox : {}, - normalize: function (expr) { - if (cache[expr]) {return cache[expr];} - var subx = []; - var normalized = expr.replace(/[\['](\??\(.*?\))[\]']/g, function ($0,$1) {return '[#' + (subx.push($1) - 1) + ']';}) - .replace(/'?\.'?|\['?/g, ';') - .replace(/(?:;)?(\^+)(?:;)?/g, function (_, ups) {return ';' + ups.split('').join(';') + ';';}) - .replace(/;;;|;;/g, ';..;') - .replace(/;$|'?\]|'$/g, ''); - var exprList = normalized.split(';').map(function (expr) { - var match = expr.match(/#([0-9]+)/); - return !match || !match[1] ? expr : subx[match[1]]; - }); - cache[expr] = exprList; - return cache[expr]; - }, - asPath: function (path) { - var i, n, x = path, p = '$'; - for (i = 1, n = x.length; i < n; i++) { - p += /^[0-9*]+$/.test(x[i]) ? ('[' + x[i] + ']') : ("['" + x[i] + "']"); - } - return p; - }, - trace: function (expr, val, path) { - // No expr to follow? return path and value as the result of this trace branch - if (!expr.length) {return [{path: path, value: val}];} - - var loc = expr[0], x = expr.slice(1); - // The parent sel computation is handled in the frame above using the - // ancestor object of val - if (loc === '^') {return path.length ? [{path: path.slice(0, -1), expr: x, isParentSelector: true}] : [];} - - // We need to gather the return value of recursive trace calls in order to - // do the parent sel computation. - var ret = []; - function addRet (elems) {ret = ret.concat(elems);} - - if (val && val.hasOwnProperty(loc)) { // simple case, directly follow property - addRet(P.trace(x, val[loc], push(path, loc))); - } - else if (loc === '*') { // any property - P.walk(loc, x, val, path, function (m, l, x, v, p) { - addRet(P.trace(unshift(m, x), v, p)); - }); - } - else if (loc === '..') { // all child properties - addRet(P.trace(x, val, path)); - P.walk(loc, x, val, path, function (m, l, x, v, p) { - if (typeof v[m] === 'object') { - addRet(P.trace(unshift('..', x), v[m], push(p, m))); - } - }); - } - else if (loc[0] === '(') { // [(expr)] - addRet(P.trace(unshift(P.eval(loc, val, path[path.length], path), x), val, path)); - } - else if (loc.indexOf('?(') === 0) { // [?(expr)] - P.walk(loc, x, val, path, function (m, l, x, v, p) { - if (P.eval(l.replace(/^\?\((.*?)\)$/, '$1'), v[m], m, path)) { - addRet(P.trace(unshift(m, x), v, p)); - } - }); - } - else if (loc.indexOf(',') > -1) { // [name1,name2,...] - var parts, i; - for (parts = loc.split(','), i = 0; i < parts.length; i++) { - addRet(P.trace(unshift(parts[i], x), val, path)); - } - } - else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) { // [start:end:step] Python slice syntax - addRet(P.slice(loc, x, val, path)); +function JSONPath (opts, obj, expr) { + if (!(this instanceof JSONPath)) { + try { + return new JSONPath(opts, obj, expr); + } + catch (e) { + if (!e.avoidNew) { + throw e; } + return e.value; + } + } - // We check the resulting values for parent selections. For parent - // selections we discard the value object and continue the trace with the - // current val object - return ret.reduce(function (all, ea) { - return all.concat(ea.isParentSelector ? P.trace(ea.expr, val, ea.path) : [ea]); - }, []); - }, - walk: function (loc, expr, val, path, f) { - var i, n, m; - if (Array.isArray(val)) { - for (i = 0, n = val.length; i < n; i++) { - f(i, loc, expr, val, path); - } - } - else if (typeof val === 'object') { - for (m in val) { - if (val.hasOwnProperty(m)) { - f(m, loc, expr, val, path); - } - } - } - }, - slice: function (loc, expr, val, path) { - if (!Array.isArray(val)) {return;} - var i, - len = val.length, parts = loc.split(':'), - start = (parts[0] && parseInt(parts[0], 10)) || 0, - end = (parts[1] && parseInt(parts[1], 10)) || len, - step = (parts[2] && parseInt(parts[2], 10)) || 1; - start = (start < 0) ? Math.max(0, start + len) : Math.min(len, start); - end = (end < 0) ? Math.max(0, end + len) : Math.min(len, end); - var ret = []; - for (i = start; i < end; i += step) { - ret = ret.concat(P.trace(unshift(i, expr), val, path)); - } - return ret; - }, - eval: function (code, _v, _vname, path) { - if (!$ || !_v) {return false;} - if (code.indexOf('@path') > -1) { - P.sandbox._path = P.asPath(path.concat([_vname])); - code = code.replace(/@path/g, '_path'); - } - if (code.indexOf('@') > -1) { - P.sandbox._v = _v; - code = code.replace(/@/g, '_v'); - } - try { - return vm.runInNewContext(code, P.sandbox); - } - catch(e) { - console.log(e); - throw new Error('jsonPath: ' + e.message + ': ' + code); - } + opts = opts || {}; + var objArgs = opts.hasOwnProperty('json') && opts.hasOwnProperty('path'); + this.resultType = (opts.resultType && opts.resultType.toLowerCase()) || 'value'; + this.flatten = opts.flatten || false; + this.wrap = opts.hasOwnProperty('wrap') ? opts.wrap : true; + this.sandbox = opts.sandbox || {}; + + if (opts.autostart !== false) { + var ret = this.evaluate((objArgs ? opts.json : obj), (objArgs ? opts.path : expr)); + if (!ret || typeof reg !== 'object') { + throw {avoidNew: true, value: ret, message: "JSONPath should not be called with 'new'"}; } - }; + } +} + +// PUBLIC METHODS - var resultType = P.resultType.toLowerCase(); - if (expr && obj && (resultType === 'value' || resultType === 'path')) { - var exprList = P.normalize(expr); +JSONPath.prototype.evaluate = function (obj, expr) { + var self = this; + this._obj = obj; + if (expr && obj && (this.resultType === 'value' || this.resultType === 'path')) { + var exprList = this._normalize(expr); if (exprList[0] === '$' && exprList.length > 1) {exprList.shift();} - var result = P.trace(exprList, obj, ['$']); + var result = this._trace(exprList, obj, ['$']); result = result.filter(function (ea) { return ea && !ea.isParentSelector; }); - if (!result.length) {return P.wrap ? [] : false;} - if (result.length === 1 && !P.wrap && !Array.isArray(result[0].value)) {return result[0][resultType] || false;} + if (!result.length) {return this.wrap ? [] : false;} + if (result.length === 1 && !this.wrap && !Array.isArray(result[0].value)) {return result[0][this.resultType] || false;} return result.reduce(function (result, ea) { - var valOrPath = ea[resultType]; - if (resultType === 'path') {valOrPath = P.asPath(valOrPath);} - if (P.flatten && Array.isArray(valOrPath)) { + var valOrPath = ea[self.resultType]; + if (self.resultType === 'path') {valOrPath = self._asPath(valOrPath);} + if (self.flatten && Array.isArray(valOrPath)) { result = result.concat(valOrPath); } else { result.push(valOrPath); @@ -188,13 +84,157 @@ function jsonPath (obj, expr, arg) { return result; }, []); } -} +}; + +// PRIVATE METHODS + +JSONPath.prototype._normalize = function (expr) { + if (cache[expr]) {return cache[expr];} + var subx = []; + var normalized = expr.replace(/[\['](\??\(.*?\))[\]']/g, function ($0, $1) {return '[#' + (subx.push($1) - 1) + ']';}) + .replace(/'?\.'?|\['?/g, ';') + .replace(/(?:;)?(\^+)(?:;)?/g, function ($0, ups) {return ';' + ups.split('').join(';') + ';';}) + .replace(/;;;|;;/g, ';..;') + .replace(/;$|'?\]|'$/g, ''); + var exprList = normalized.split(';').map(function (expr) { + var match = expr.match(/#([0-9]+)/); + return !match || !match[1] ? expr : subx[match[1]]; + }); + cache[expr] = exprList; + return cache[expr]; +}; + +JSONPath.prototype._asPath = function (path) { + var i, n, x = path, p = '$'; + for (i = 1, n = x.length; i < n; i++) { + p += /^[0-9*]+$/.test(x[i]) ? ('[' + x[i] + ']') : ("['" + x[i] + "']"); + } + return p; +}; + +JSONPath.prototype._trace = function (expr, val, path) { + // No expr to follow? return path and value as the result of this trace branch + var self = this; + if (!expr.length) {return [{path: path, value: val}];} + + var loc = expr[0], x = expr.slice(1); + // The parent sel computation is handled in the frame above using the + // ancestor object of val + if (loc === '^') {return path.length ? [{path: path.slice(0, -1), expr: x, isParentSelector: true}] : [];} -if (typeof exports === 'undefined') { - window.jsonPath = {eval: jsonPath}; + // We need to gather the return value of recursive trace calls in order to + // do the parent sel computation. + var ret = []; + function addRet (elems) {ret = ret.concat(elems);} + + if (val && val.hasOwnProperty(loc)) { // simple case, directly follow property + addRet(this._trace(x, val[loc], push(path, loc))); + } + else if (loc === '*') { // any property + this._walk(loc, x, val, path, function (m, l, x, v, p) { + addRet(self._trace(unshift(m, x), v, p)); + }); + } + else if (loc === '..') { // all child properties + addRet(this._trace(x, val, path)); + this._walk(loc, x, val, path, function (m, l, x, v, p) { + if (typeof v[m] === 'object') { + addRet(self._trace(unshift('..', x), v[m], push(p, m))); + } + }); + } + else if (loc[0] === '(') { // [(expr)] + addRet(this._trace(unshift(this._eval(loc, val, path[path.length], path), x), val, path)); + } + else if (loc.indexOf('?(') === 0) { // [?(expr)] + this._walk(loc, x, val, path, function (m, l, x, v, p) { + if (self._eval(l.replace(/^\?\((.*?)\)$/, '$1'), v[m], m, path)) { + addRet(self._trace(unshift(m, x), v, p)); + } + }); + } + else if (loc.indexOf(',') > -1) { // [name1,name2,...] + var parts, i; + for (parts = loc.split(','), i = 0; i < parts.length; i++) { + addRet(this._trace(unshift(parts[i], x), val, path)); + } + } + else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) { // [start:end:step] Python slice syntax + addRet(this._slice(loc, x, val, path)); + } + + // We check the resulting values for parent selections. For parent + // selections we discard the value object and continue the trace with the + // current val object + return ret.reduce(function (all, ea) { + return all.concat(ea.isParentSelector ? self._trace(ea.expr, val, ea.path) : [ea]); + }, []); +}; + +JSONPath.prototype._walk = function (loc, expr, val, path, f) { + var i, n, m; + if (Array.isArray(val)) { + for (i = 0, n = val.length; i < n; i++) { + f(i, loc, expr, val, path); + } + } + else if (typeof val === 'object') { + for (m in val) { + if (val.hasOwnProperty(m)) { + f(m, loc, expr, val, path); + } + } + } +}; + +JSONPath.prototype._slice = function (loc, expr, val, path) { + if (!Array.isArray(val)) {return;} + var i, + len = val.length, parts = loc.split(':'), + start = (parts[0] && parseInt(parts[0], 10)) || 0, + end = (parts[1] && parseInt(parts[1], 10)) || len, + step = (parts[2] && parseInt(parts[2], 10)) || 1; + start = (start < 0) ? Math.max(0, start + len) : Math.min(len, start); + end = (end < 0) ? Math.max(0, end + len) : Math.min(len, end); + var ret = []; + for (i = start; i < end; i += step) { + ret = ret.concat(this._trace(unshift(i, expr), val, path)); + } + return ret; +}; + +JSONPath.prototype._eval = function (code, _v, _vname, path) { + if (!this._obj || !_v) {return false;} + if (code.indexOf('@path') > -1) { + this.sandbox._$_path = this._asPath(path.concat([_vname])); + code = code.replace(/@path/g, '_$_path'); + } + if (code.indexOf('@') > -1) { + this.sandbox._$_v = _v; + code = code.replace(/@/g, '_$_v'); + } + try { + return vm.runInNewContext(code, this.sandbox); + } + catch(e) { + console.log(e); + throw new Error('jsonPath: ' + e.message + ': ' + code); + } +}; + +// For backward compatibility (deprecated) +JSONPath.eval = function (obj, expr, opts) { + return JSONPath(opts, obj, expr); +}; + +if (typeof module === 'undefined') { + window.jsonPath = { // Deprecated + eval: JSONPath.eval + }; + window.JSONPath = JSONPath; } else { - exports.eval = jsonPath; + module.exports = JSONPath; } }(typeof require === 'undefined' ? null : require)); diff --git a/package.json b/package.json index 1d395d224ad996f11452e86258a8707b044d40da..7fbeacfea64b7ce28c2b30c0a63354062a492ac8 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "email": "robert.krahn@gmail.com" } ], - "version": "0.10.0", + "version": "0.11.0", "repository": { "type": "git", "url": "git://github.com/s3u/JSONPath.git" diff --git a/test/test.arr.js b/test/test.arr.js index 4314fd236d4b61fd024e260948164fd887d5a0b5..d206071ee6f20bca2dde1c633cba6c54fc9c8519 100644 --- a/test/test.arr.js +++ b/test/test.arr.js @@ -1,4 +1,4 @@ -var jsonpath = require("../").eval, +var JSONPath = require('../'), testCase = require('nodeunit').testCase var json = { @@ -19,16 +19,16 @@ var json = { }; module.exports = testCase({ - "get single": function (test) { + 'get single': function (test) { var expected = json.store.book; - var result = jsonpath(json, "store.book", {flatten: true, wrap: false}); + var result = JSONPath({json: json, path: 'store.book', flatten: true, wrap: false}); test.deepEqual(expected, result); test.done(); }, - "get arr": function (test) { + 'get arr': function (test) { var expected = json.store.books; - var result = jsonpath(json, "store.books", {flatten: true, wrap: false}); + var result = JSONPath({json: json, path: 'store.books', flatten: true, wrap: false}); test.deepEqual(expected, result); test.done(); } diff --git a/test/test.at_and_dollar.js b/test/test.at_and_dollar.js index e99d7dbf6d6770ab57a257f75c5372bed5c547cb..9c0eb42fae2f08aac4ed16e0d93cf20f2d253ef6 100644 --- a/test/test.at_and_dollar.js +++ b/test/test.at_and_dollar.js @@ -1,5 +1,5 @@ -var jsonpath = require("../").eval - , testCase = require('nodeunit').testCase +var JSONPath = require('../'), + testCase = require('nodeunit').testCase var t1 = { @@ -22,29 +22,29 @@ module.exports = testCase({ // ============================================================================ - "test undefined, null": function(test) { + 'test undefined, null': function(test) { // ============================================================================ test.expect(5); - test.equal(undefined, jsonpath(undefined, "foo")); - test.equal(null, jsonpath(null, "foo")); - test.equal(undefined, jsonpath({}, "foo")[0]); - test.equal(undefined, jsonpath({ a: "b" }, "foo")[0]); - test.equal(undefined, jsonpath({ a: "b" }, "foo")[100]); + test.equal(undefined, JSONPath({json: undefined, path: 'foo'})); + test.equal(null, JSONPath({json: null, path: 'foo'})); + test.equal(undefined, JSONPath({json: {}, path: 'foo'})[0]); + test.equal(undefined, JSONPath({json: { a: 'b' }, path: 'foo'})[0]); + test.equal(undefined, JSONPath({json: { a: 'b' }, path: 'foo'})[100]); test.done(); }, // ============================================================================ - "test $ and @": function(test) { + 'test $ and @': function(test) { // ============================================================================ test.expect(7); - test.equal(t1["$"], jsonpath(t1, "\$")[0]); - test.equal(t1["$"], jsonpath(t1, "$")[0]); - test.equal(t1["a$a"], jsonpath(t1, "a$a")[0]); - test.equal(t1["@"], jsonpath(t1, "\@")[0]); - test.equal(t1["@"], jsonpath(t1, "@")[0]); - test.equal(t1["$"]["@"], jsonpath(t1, "$.$.@")[0]); - test.equal(undefined, jsonpath(t1, "\@")[1]); + test.equal(t1['$'], JSONPath({json: t1, path: '\$'})[0]); + test.equal(t1['$'], JSONPath({json: t1, path: '$'})[0]); + test.equal(t1['a$a'], JSONPath({json: t1, path: 'a$a'})[0]); + test.equal(t1['@'], JSONPath({json: t1, path: '\@'})[0]); + test.equal(t1['@'], JSONPath({json: t1, path: '@'})[0]); + test.equal(t1['$']['@'], JSONPath({json: t1, path: '$.$.@'})[0]); + test.equal(undefined, JSONPath({json: t1, path: '\@'})[1]); test.done(); } diff --git a/test/test.eval.js b/test/test.eval.js index 8da68e90879e4bc7af97071482a150c92c893a4e..98b3459844f5f7cba4e4d633d38ff6b0f7e43eea 100644 --- a/test/test.eval.js +++ b/test/test.eval.js @@ -1,4 +1,4 @@ -var jsonpath = require("../").eval, +var JSONPath = require('../'), testCase = require('nodeunit').testCase var json = { @@ -26,19 +26,19 @@ var json = { module.exports = testCase({ - "multi statement eval": function (test) { + 'multi statement eval': function (test) { var expected = json.store.books[0]; - var selector = "$..[?(" - + "var sum = @.price && @.price[0]+@.price[1];" - + "sum > 20;)]" - var result = jsonpath(json, selector, {wrap: false}); + var selector = '$..[?(' + + 'var sum = @.price && @.price[0]+@.price[1];' + + 'sum > 20;)]' + var result = JSONPath({json: json, path: selector, wrap: false}); test.deepEqual(expected, result); test.done(); }, - "accessing current path": function (test) { + 'accessing current path': function (test) { var expected = json.store.books[1]; - var result = jsonpath(json, "$..[?(@path==\"$['store']['books'][1]\")]", {wrap: false}); + var result = JSONPath({json: json, path: "$..[?(@path==\"$['store']['books'][1]\")]", wrap: false}); test.deepEqual(expected, result); test.done(); } diff --git a/test/test.examples.js b/test/test.examples.js index 053272568478962fde72051223f76427f7679b56..a28e3bbfa2b9d121d521151f70db5847b79c2060 100644 --- a/test/test.examples.js +++ b/test/test.examples.js @@ -1,10 +1,10 @@ -var jsonpath = require("../").eval - , testCase = require('nodeunit').testCase +var JSONPath = require('../'), + testCase = require('nodeunit').testCase // tests based on examples at http://goessner.net/articles/JsonPath/ var json = {"store": { - "book": [ + "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", @@ -37,123 +37,123 @@ var json = {"store": { module.exports = testCase({ - - // ============================================================================ - "wildcards": function(test) { - // ============================================================================ + + // ============================================================================ + 'wildcards': function(test) { + // ============================================================================ test.expect(1); var books = json.store.book; var expected = [books[0].author, books[1].author, books[2].author, books[3].author]; - var result = jsonpath(json, "$.store.book[*].author"); + var result = JSONPath({json: json, path: '$.store.book[*].author'}); test.deepEqual(expected, result); - + test.done(); }, - - // ============================================================================ - "all properties, entire tree": function(test) { - // ============================================================================ + + // ============================================================================ + 'all properties, entire tree': function(test) { + // ============================================================================ test.expect(1); var books = json.store.book; var expected = [books[0].author, books[1].author, books[2].author, books[3].author]; - var result = jsonpath(json, "$..author"); + var result = JSONPath({json: json, path: '$..author'}); test.deepEqual(expected, result); - + test.done(); }, - - // ============================================================================ - "all sub properties, single level": function(test) { - // ============================================================================ + + // ============================================================================ + 'all sub properties, single level': function(test) { + // ============================================================================ test.expect(1); var expected = [json.store.book, json.store.bicycle]; - var result = jsonpath(json, "$.store.*"); + var result = JSONPath({json: json, path: '$.store.*'}); test.deepEqual(expected, result); - + test.done(); }, - // ============================================================================ - "all sub properties, entire tree": function(test) { - // ============================================================================ + // ============================================================================ + 'all sub properties, entire tree': function(test) { + // ============================================================================ test.expect(1); var books = json.store.book; var expected = [books[0].price, books[1].price, books[2].price, books[3].price, json.store.bicycle.price]; - var result = jsonpath(json, "$.store..price"); + var result = JSONPath({json: json, path: '$.store..price'}); test.deepEqual(expected, result); - + test.done(); }, - - // ============================================================================ - "n property of entire tree": function(test) { - // ============================================================================ + + // ============================================================================ + 'n property of entire tree': function(test) { + // ============================================================================ test.expect(1); var books = json.store.book; var expected = [books[2]]; - var result = jsonpath(json, "$..book[2]"); + var result = JSONPath({json: json, path: '$..book[2]'}); test.deepEqual(expected, result); - + test.done(); }, - // ============================================================================ - "last property of entire tree": function(test) { - // ============================================================================ + // ============================================================================ + 'last property of entire tree': function(test) { + // ============================================================================ test.expect(2); var books = json.store.book; var expected = [books[3]]; - var result = jsonpath(json, "$..book[(@.length-1)]"); + var result = JSONPath({json: json, path: '$..book[(@.length-1)]'}); test.deepEqual(expected, result); - - result = jsonpath(json, "$..book[-1:]"); + + result = JSONPath({json: json, path: '$..book[-1:]'}); test.deepEqual(expected, result); - + test.done(); }, - - // ============================================================================ - "range of property of entire tree": function(test) { - // ============================================================================ + + // ============================================================================ + 'range of property of entire tree': function(test) { + // ============================================================================ test.expect(2); var books = json.store.book; var expected = [books[0], books[1]]; - var result = jsonpath(json, "$..book[0,1]"); + var result = JSONPath({json: json, path: '$..book[0,1]'}); test.deepEqual(expected, result); - - result = jsonpath(json, "$..book[:2]"); + + result = JSONPath({json: json, path: '$..book[:2]'}); test.deepEqual(expected, result); - + test.done(); }, - - // ============================================================================ - "filter all properties if sub property exists,o entire tree": function(test) { - // ============================================================================ + + // ============================================================================ + 'filter all properties if sub property exists, of entire tree': function(test) { + // ============================================================================ test.expect(1); var books = json.store.book; var expected = [books[2], books[3]]; - var result = jsonpath(json, "$..book[?(@.isbn)]"); + var result = JSONPath({json: json, path: '$..book[?(@.isbn)]'}); test.deepEqual(expected, result); - + test.done(); }, - - // ============================================================================ - "filter all properties if sub property greater than of entire tree": function(test) { - // ============================================================================ + + // ============================================================================ + 'filter all properties if sub property greater than of entire tree': function(test) { + // ============================================================================ test.expect(1); var books = json.store.book; var expected = [books[0], books[2]]; - var result = jsonpath(json, "$..book[?(@.price<10)]"); + var result = JSONPath({json: json, path: '$..book[?(@.price<10)]'}); test.deepEqual(expected, result); - + test.done(); }, - - // ============================================================================ - "all properties of a json structure": function(test) { - // ============================================================================ + + // ============================================================================ + 'all properties of a JSON structure': function(test) { + // ============================================================================ // test.expect(1); var expected = [ json.store, @@ -165,13 +165,13 @@ module.exports = testCase({ expected.push(json.store.bicycle.color); expected.push(json.store.bicycle.price); - var result = jsonpath(json, "$..*"); + var result = JSONPath({json: json, path: '$..*'}); test.deepEqual(expected, result); - + test.done(); } - - - - + + + + }); diff --git a/test/test.html b/test/test.html index f9089200ec6bbf50c22ac689dfe634e0c1ef226a..505fd4f35ac098851b9248889680e1eb771cba24 100644 --- a/test/test.html +++ b/test/test.html @@ -7,54 +7,61 @@ <script src="../node_modules/nodeunit/dist/browser/nodeunit.js"></script> <script src="../lib/jsonpath.js"></script> <script> + /*jslint nomen: true, white: true, browser:true, plusplus: true, evil:true*/ + /*global nodeunit, JSONPath, ActiveXObject*/ // helper to get all the test cases + 'use strict'; var suites = [], _testCase = nodeunit.testCase; nodeunit.testCase = function(tc) { - suites.push(tc); return _testCase(tc) }; + suites.push(tc); + return _testCase(tc); + }; // stubs to load nodejs tests function require(path) { - if (path === 'nodeunit') return nodeunit; - if (path.match(/^\.\.\/?$/)) return jsonPath; + if (path === 'nodeunit') {return nodeunit;} + if (path.match(/^\.\.\/?$/)) {return JSONPath;} } var module = {exports: {}}; - </script> - <script> + + // synchronous load function for JS code, uses XMLHttpRequest abstraction from // http://www.quirksmode.org/js/xmlhttp.html // Since the tests are written in node.js style we need to wrap their code into // a function, otherwise they would pollute the global NS and interfere with each // other function get(url, callback) { - function sendRequest(url,callback) { - var req = createXMLHTTPObject(); - req.open("GET",url,false/*sync*/); - req.onreadystatechange = function () { req.readyState == 4 && callback(req); } - if (req.readyState != 4) req.send(); - }; function createXMLHTTPObject() { - var XMLHttpFactories = [ - function () {return new XMLHttpRequest()}, - function () {return new ActiveXObject("Msxml2.XMLHTTP")}, - function () {return new ActiveXObject("Msxml3.XMLHTTP")}, - function () {return new ActiveXObject("Microsoft.XMLHTTP")}]; - for (var i=0;i<XMLHttpFactories.length;i++) - try { return XMLHttpFactories[i](); } catch (e) { } + var i, XMLHttpFactories = [ + function () {return new XMLHttpRequest();}, + function () {return new ActiveXObject('Msxml2.XMLHTTP');}, + function () {return new ActiveXObject('Msxml3.XMLHTTP');}, + function () {return new ActiveXObject('Microsoft.XMLHTTP');}]; + for (i = 0; i < XMLHttpFactories.length; i++) { + try {return XMLHttpFactories[i]();} + catch (ignore) {} + } return false; } + function sendRequest(url,callback) { + var req = createXMLHTTPObject(); + req.open('GET', url, false /*sync*/); + req.onreadystatechange = function () { if (req.readyState === 4) { callback(req); } }; + if (req.readyState !== 4) {req.send();} + } sendRequest(url, callback); } - function loadJS(url) { get(url, function(req) { new Function(req.responseText)(); })} + function loadJS(url) { get(url, function(req) { new Function(req.responseText)(); });} </script> </head> <body> <h1 id="nodeunit-header">JSONPath Tests</h1> <script> loadJS('test.arr.js'); - loadJS("test.at_and_dollar.js"); - loadJS("test.eval.js"); - loadJS("test.examples.js"); - loadJS("test.intermixed.arr.js"); - loadJS("test.parent-selector.js"); + loadJS('test.at_and_dollar.js'); + loadJS('test.eval.js'); + loadJS('test.examples.js'); + loadJS('test.intermixed.arr.js'); + loadJS('test.parent-selector.js'); nodeunit.run(suites); </script> </body> diff --git a/test/test.intermixed.arr.js b/test/test.intermixed.arr.js index 1cf622d5d798fb538c2a7164e03b460f2be06c45..7a9eaa035da20429f0c38a88bd63d424110fe19a 100644 --- a/test/test.intermixed.arr.js +++ b/test/test.intermixed.arr.js @@ -1,4 +1,4 @@ -var jsonpath = require("../").eval, +var JSONPath = require('../'), testCase = require('nodeunit').testCase // tests based on examples at http://goessner.net/articles/JsonPath/ @@ -39,13 +39,13 @@ var json = {"store":{ module.exports = testCase({ // ============================================================================ - "all sub properties, entire tree":function (test) { + 'all sub properties, entire tree': function (test) { // ============================================================================ test.expect(1); var books = json.store.book; var expected = [books[1].price, books[2].price, books[3].price, json.store.bicycle.price]; expected = books[0].price.concat(expected); - var result = jsonpath(json, "$.store..price", {flatten: true}); + var result = JSONPath({json: json, path: '$.store..price', flatten: true}); test.deepEqual(expected, result); test.done(); diff --git a/test/test.parent-selector.js b/test/test.parent-selector.js index 678c9e3f883b8a5d19d3cd1e206d2fe42d9ae34f..6f9c422495554c96ca6643a37ccec5ce7d4c9514 100644 --- a/test/test.parent-selector.js +++ b/test/test.parent-selector.js @@ -1,4 +1,4 @@ -var jsonpath = require("../").eval, +var JSONPath = require('../'), testCase = require('nodeunit').testCase var json = { @@ -14,49 +14,49 @@ var json = { module.exports = testCase({ // ============================================================================ - "simple parent selection": function(test) { + 'simple parent selection': function(test) { // ============================================================================ test.expect(1); - var result = jsonpath(json, "$.children[0]^", {flatten: true}); + var result = JSONPath({json: json, path: '$.children[0]^', flatten: true}); test.deepEqual(json.children, result); test.done(); }, // ============================================================================ - "parent selection with multiple matches": function(test) { + 'parent selection with multiple matches': function(test) { // ============================================================================ test.expect(1); var expected = [json.children,json.children]; - var result = jsonpath(json, "$.children[1:3]^"); + var result = JSONPath({json: json, path: '$.children[1:3]^'}); test.deepEqual(expected, result); test.done(); }, // ============================================================================ - "select sibling via parent": function(test) { + 'select sibling via parent': function(test) { // ============================================================================ test.expect(1); var expected = [{"name": "child3_2"}]; - var result = jsonpath(json, "$..[?(@.name && @.name.match(/3_1$/))]^[?(@.name.match(/_2$/))]"); + var result = JSONPath({json: json, path: '$..[?(@.name && @.name.match(/3_1$/))]^[?(@.name.match(/_2$/))]'}); test.deepEqual(expected, result); test.done(); }, // ============================================================================ - "parent parent parent": function(test) { + 'parent parent parent': function(test) { // ============================================================================ test.expect(1); var expected = json.children[0].children; - var result = jsonpath(json, "$..[?(@.name && @.name.match(/1_1$/))].name^^", {flatten: true}); + var result = JSONPath({json: json, path: '$..[?(@.name && @.name.match(/1_1$/))].name^^', flatten: true}); test.deepEqual(expected, result); test.done(); }, // ============================================================================ - "no such parent": function(test) { + 'no such parent': function(test) { // ============================================================================ test.expect(1); - var result = jsonpath(json, "name^^"); + var result = JSONPath({json: json, path: 'name^^'}); test.deepEqual([], result); test.done(); }