src/relayer/jsonpath.js
/* JSONPath 0.8.0 - XPath for JSON
*
* Copyright (c) 2007 Stefan Goessner (goessner.net)
* Licensed under the MIT (MIT-LICENSE.txt) licence.
*/
if (!Array.isArray) {
Array.isArray = function(vArg) {
return Object.prototype.toString.call(vArg) === "[object Array]";
};
}
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; }
export default function jsonPath(obj, expr, arg) {
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(_, front, ups, back) { 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 exprList;
},
asPath: function(path) {
var x = path, p = "$";
for (var 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) {
function addRet(elems) { ret = ret.concat(elems); }
// 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 === '^'){
if(path.length){
return [{path: path.slice(0,-1), expr: x, isParentSelector: true}];
} else {
return [];
}
}
// we need to gather the return value of recursive trace calls in order to
// do the parent sel computation.
var ret = [];
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 chid 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.evaluate(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.evaluate(l.replace(/^\?\((.*?)\)$/,"$1"),v[m],m, path)) {
addRet(P.trace(unshift(m,x),v,p));
}
});
}
else if (loc.indexOf(',') > -1) { // [name1,name2,...]
for (var 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));
}
// 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) {
if (Array.isArray(val)) {
for (var i = 0, n = val.length; i < n; i++) {
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 (!Array.isArray(val)){ return; }
var len = val.length, parts = loc.split(':'),
start = (parts[0] && parseInt(parts[0])) || 0,
end = (parts[1] && parseInt(parts[1])) || len,
step = (parts[2] && parseInt(parts[2])) || 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 (var i = start; i < end; i += step) {
ret = ret.concat(P.trace(unshift(i,expr), val, path));
}
return ret;
},
evaluate: function(code, _v, _vname, path) {
if( false ) { //supporting this requires allowing JS eval - very dodgy
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);
}
} else {
var msg = `Refusing to eval: '${code}'`;
console.log(msg);
throw new Error(`jsonPath: ${msg}`);
}
}
};
var $ = obj;
var resultType = P.resultType.toLowerCase();
if( obj ){
if (expr && (resultType == "value" || resultType == "path")) {
var exprList = P.normalize(expr);
if (exprList[0] === "$" && exprList.length > 1){
exprList.shift();
}
var result = P.trace(exprList, obj, ["$"]);
result = result.filter(function(ea) { return ea && !ea.isParentSelector; });
if (!result.length){
if(P.wrap){
return [];
} else {
return false;
}
}
if (result.length === 1 && !P.wrap && (resultType == 'path' || !Array.isArray(result[0].value))) {
var ret = result[0][resultType];
if(typeof ret === "undefined"){
return false;
} else {
return ret;
}
}
return result.reduce(function(result, ea) {
var valOrPath = ea[resultType];
// For our purposes we don't return paths but rather arrays of segments
// if (resultType === 'path'){ valOrPath = P.asPath(valOrPath); }
if (P.flatten && Array.isArray(valOrPath)) {
result = result.concat(valOrPath);
} else {
result.push(valOrPath);
}
return result;
}, []);
}
} else {
return obj;
}
}