Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active August 8, 2023 20:47
Show Gist options
  • Save dfkaye/e1b7ee333a760003d4b9 to your computer and use it in GitHub Desktop.
Save dfkaye/e1b7ee333a760003d4b9 to your computer and use it in GitHub Desktop.
JSONPath.js modified ~ replace eval() in filter() with operate() and compare() methods
// STOLEN FROM GOOGLE CODE REPO 01 NOV 2014
// MODIFIED FOR
// + 1 Nov 2104 - READABILITY
// + 3 NOV 2014 - use (content security policy) no-eval() approach, see filter() method
// + 10 Dec 2014 - revised filter() with switch
// + 23 Feb 2015 - revised operate() and compare() methods
// + 28 July 2015 - bug fix: `attr` var in filter() wasn't `var'd`
// + 9 May 2018 - show test output
/* JSONPath 0.8.5 - XPath for JSON
*
* Copyright (c) 2007 Stefan Goessner (goessner.net)
* Licensed under the MIT (MIT-LICENSE.txt) licence.
*
* Proposal of Chris Zyp goes into version 0.9.x
* Issue 7 resolved
*/
function jsonPath(obj, expr, arg) {
var P = {
resultType: arg && arg.resultType || "VALUE",
result: [],
normalize: function(expr) {
var subx = [];
return expr.replace(/[\['](\??\(.*?\))[\]']|\['(.*?)'\]/g,
function ($0, $1, $2) {
return "[#" + (subx.push($1 || $2) -1) + "]";
}) /* http://code.google.com/p/jsonpath/issues/detail?id=4 */
.replace(/'?\.'?|\['?/g, ";")
.replace(/;;;|;;/g, ";..;")
.replace(/;$|'?\]|'$/g, "")
.replace(/#([0-9]+)/g, function ($0 , $1) {
return subx[$1];
});
},
asPath: function(path) {
var x = path.split(";"), p = "$";
for (var i = 1, n = x.length; i < n; i++) {
p += /^[0-9*]+$/.test(x[i]) ? ("[" + x[i] + "]") : ("['" + x[i] + "']");
}
return p;
},
store: function(p, v) {
if (p) {
P.result[P.result.length] = P.resultType == "PATH" ? P.asPath(p) : v;
}
return !!p;
},
trace: function(expr, val, path) {
if (expr !== "") {
var x = expr.split(";"), loc = x.shift();
x = x.join(";");
if (val && val.hasOwnProperty(loc)) {
P.trace(x, val[loc], path + ";" + loc);
}
else if (loc === "*") {
P.walk(loc, x, val, path, function(m, l, x, v, p) {
P.trace(m + ";" + x, v, p);
});
}
else if (loc === "..") {
P.trace(x, val, path);
P.walk(loc, x, val, path, function(m, l, x, v, p) {
typeof v[m] === "object" && P.trace("..;" + x, v[m], p + ";" + m);
});
}
else if (/^\(.*?\)$/.test(loc)) {// [(expr)]
// P.trace(P.eval(loc, val, path.substr(path.lastIndexOf(";") + 1)) +
// ";" + x, val, path);
P.trace(P.filter(loc, val, path.substr(path.lastIndexOf(";") + 1)) +
";" + x, val, path);
}
else if (/^\?\(.*?\)$/.test(loc)) { // [?(expr)]
P.walk(loc, x, val, path, function (m, l, x, v, p) {
// if (P.eval(l.replace(/^\?\((.*?)\)$/, "$1"),
// v instanceof Array ? v[m] : v, m)) {
if (P.filter(l.replace(/^\?\((.*?)\)$/, "$1"),
v instanceof Array ? v[m] : v, m)) {
P.trace(m + ";" + x, v, p);
}
}); // issue 5 resolved
}
else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc)) { // [start:end:step] phyton slice syntax
P.slice(loc, x, val, path);
}
else if (/,/.test(loc)) { // [name1,name2,...]
for (var s = loc.split(/'?,'?/), i = 0, n = s.length; i < n; i++) {
P.trace(s[i] + ";" + x, val, path);
}
}
}
else {
P.store(path, val);
}
},
walk: function(loc, expr, val, path, f) {
if (val instanceof Array) {
for (var i = 0, n = val.length; i < n; i++) {
if (i in val) {
f(i, loc, expr, val, path);
}
}
}
else if (typeof val === "object") {
for (var m in val) {
if (val.hasOwnProperty(m)) {
f(m, loc, expr, val, path);
}
}
}
},
slice: function(loc, expr, val, path) {
if (val instanceof Array) {
var len = val.length, start = 0, end = len, step = 1;
loc.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g,
function ($0, $1, $2, $3) {
start = parseInt($1 || start, 10);
end = parseInt($2 || end, 10);
step = parseInt($3 || step, 10);
});
start = (start < 0) ? Math.max(0, start + len) : Math.min(len, start);
end = (end < 0) ? Math.max(0, end + len) : Math.min(len, end);
for (var i = start; i < end; i += step) {
P.trace(i + ";" + expr, val, path);
}
}
},
// eval: function(x, _v, _vname) { //console.warn(_vname); // _vname is index
filter: function(x, _v, _vname) {
// 01 NOV 2014: filter() replaces eval() for CSP
var s = x.replace(/(^|[^\\])@/g, "$1_v").replace(/\\@/g, "@"),
p = s.replace('_v.', ''),
test = false;
var op, comp, attr;
if ($ && _v) {
attr = /\([^\)]+\)$/.exec(p);
attr && (attr = attr[0].replace(/[\(\s\)]/g, ''));
// console.warn(!!attr);
if (attr && (op = /\-|\+|\*|\/|\%/.exec(attr))) {
test = operate(_v, op[0], attr);
}
else if (comp = /==|\<=|\>=|\<|\>/.exec(p)) {
test = compare(_v, comp[0], p);
}
else {
test = p in _v;
}
}
return test;
// try {
// return $ && _v && eval(s);
// } // issue 7 : resolved ..
// catch(e) {
// throw new SyntaxError("jsonPath: " + e.message + ": " +
// x.replace(/(^|[^\\])@/g, "$1_v")
// .replace(/\\@/g, "@"));
// } // issue 7 : resolved ..
}
};
var operate = function(context, op, attr) {
// \-|\+|\*|\/|\%
var attrs = attr.split(op),
a = context[attrs[0]],
b = attrs[1];
switch(op) {
case '-' : return a - b;
case '+' : return a + b;
case '*' : return a * b;
case '/' : return a / b;
case '%' : return a % b;
default : return false;
}
};
var compare = function (context, comp, p) {
// ==|\<=|\>=|\<|\>
var attrs = p.replace(/\s/g, '').split(comp),
a = context[attrs[0]],
b = attrs[1];
switch(comp) {
case '==' : return a == b;
case '<=' : return a <= b;
case '>=' : return a >= b;
case '<' : return a < b;
case '>' : return a > b;
default : return false;
}
};
var $ = obj;
if (expr && obj && (P.resultType == "VALUE" || P.resultType == "PATH")) {
P.trace(P.normalize(expr).replace(/^\$;?/, ""), obj, "$"); // issue 6 resolved
return P.result.length ? P.result : false;
}
}
var data = {
"store": {
"book": [
{ "category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{ "category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{ "category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{ "category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
};
console.log(jsonPath(data, "$..author", {resultType:"PATH"}));
// [
// "$['store']['book'][0]['author']",
// "$['store']['book'][1]['author']",
// "$['store']['book'][2]['author']",
// "$['store']['book'][3]['author']"
// ]
console.log(jsonPath(data, "$..book[?(@.isbn)]"));
// [
// { author: "Herman Melville", category: "fiction", isbn: "0-553-21311-3", price: 8.99, title: "Moby Dick" },
// { author: "J. R. R. Tolkien", category: "fiction", isbn: "0-395-19395-8", price: 22.99, title: "The Lord of the Rings" }
// ]
console.log(jsonPath(data, "$..book[(@.length - 2)]"));
// [
// { author: "Herman Melville", category: "fiction", isbn: "0-553-21311-3", price: 8.99, title: "Moby Dick" }
// ]
console.log(jsonPath(data, "$..book[?(@.price > 12)]"));
// [
// { author: "Evelyn Waugh", category: "fiction", price: 12.99, title: "Sword of Honour" },
// { author: "J. R. R. Tolkien", category: "fiction", isbn: "0-395-19395-8", price: 22.99, title: "The Lord of the Rings" }
// ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment