Open b4rd opened 4 years ago
I need this too, have a UI tool where users can input JSONPath that will be evaluated by a backend process processing JSONs. I don't mind evaluating in the UI, but an invalid JSONPath only fails if the JSON provided matches the path to the invalid point otherwise it just returns false. Example:
JSONPath: $.properties.products[?(@.product_id.match(/prd1|prd2/)].price
Note one parentheses is missing in the above path
If i do:
JSONPath({ path: data.revenue, json: {});
I don't any indication of the faulty path. I do get the exception if i do:
JSONPath({ path: data.revenue, json: {
properties: {
products: [
{
product_id: "prdWhatever",
price: 1
}
]
}
}
});
I've looking into the code to se i could trick the lib to evaluate all the parts of the JSONPath with no success.
Did you try the option wrap: false
? undefined
should be the case when there is no matching.
Oh, sorry, I misunderstood--thought you were asking whether one could distinguish between an actual empty array being found and no matches.
The problem is that JSONPath is not schema-aware searching. What is an invalid path anyways in plain JSON? Each document can be different unless you are imposing schema restraints such as with JSON Schema. You might see if such a tool exists already, bearing in mind though that with JSON Schema, any number of possibilities could be defined in the schema, so a generic JSON Schema tool to try to detect the validity of a path could become very complex (albeit useful).
Now, yes, a feature could be added within this library to check along the way, but this project is not being very actively developed now. A PR might be welcome if simple and well-documented enough to review.
@brettz9 I think that adding a function for a strictly pre-evaluation phase syntax validation would be useful on it's own.
This would be extremely useful for validating <input>
fields and would allow us to give instant feedback to the user about the syntax error, whilst also saving us from executing an invalid query.
I believe we could tweak the expression parsing logic from Stefan Goessner original JSON path implementation to do syntax validation only - without evaluating the path against a JSON object.
/* 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: [],
/**
* normalizes the JSON path expression
* @param {*} expr the JSON path expression
* @returns the normalized JSON path expression
*/
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];});
},
/**
* digits are replaced by index access
* @param {*} path
* @returns
*/
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;
},
/**
* stores the valid path segments into paths
* @param {*} p path the path segment
* @param {*} v true if path is not null or empty and was added to store, else false
* @returns
*/
store: function(p, v) {
if (p) P.result[P.result.length] = P.resultType == "PATH" ? P.asPath(p) : v;
return !!p;
},
/**
* parses the JSON path expression. depending upon the various path segment patterns, stores the path segments into paths
* @param {*} expr
* @param {*} val
* @param {*} path
*/
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);
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)) 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);end=parseInt($2||end);step=parseInt($3||step);});
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) {
try { return $ && _v && eval(x.replace(/(^|[^\\])@/g, "$1_v").replace(/\\@/g, "@")); } // issue 7 : resolved ..
catch(e) { throw new SyntaxError("jsonPath: " + e.message + ": " + x.replace(/(^|[^\\])@/g, "$1_v").replace(/\\@/g, "@")); } // issue 7 : resolved ..
}
};
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;
}
}
Is there a way to determine whether a given path is syntactically correct?
It seems that, from the evaluation result it's not possible to tell whether there was no match or the path was simply invalid.