Skip to content

Syntax check of JSONPath without evaluation #134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
b4rd opened this issue Sep 29, 2020 · 4 comments
Open

Syntax check of JSONPath without evaluation #134

b4rd opened this issue Sep 29, 2020 · 4 comments
Labels

Comments

@b4rd
Copy link

b4rd commented Sep 29, 2020

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.

@b4rd b4rd added the Feature label Sep 29, 2020
@macebalp
Copy link

macebalp commented Dec 6, 2021

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.

@brettz9
Copy link
Collaborator

brettz9 commented Dec 9, 2021

Did you try the option wrap: false? undefined should be the case when there is no matching.

@brettz9
Copy link
Collaborator

brettz9 commented Dec 10, 2021

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.

@leviznull
Copy link

@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;
   }
} 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants