Search code examples
phpjavascriptjsonjsonpath

Craft a JSONpath expression so that it retrieves only a specific value?


I have some JSON of which the following is a small sample:

{
    "results": {
        "div": [
            {
                "class": "sylEntry",
                "div": [
                    {
                        "class": "sT",
                        "id": "sOT",
                        "p": "Mon 11/17, Computer work time"
                    },
                    {
                        "class": "des",
                        "id": "dOne",
                        "p": "All classes Siebel 0218"
                    }
                ],
                "id": "sylOne"
            }
        ]
    }
}

I would like to only retrieve the "p" content for the div element with class "sT". I would like to use a loop and doing something like this:

var arrayOfResults = $.results..div.p 

does not work because I only want to retrieve the p value for the div element with class "sT".

So how do I construct my JSONpath so that it will retrive the array of p elements that are contained within the divs class "sT".

Thanks!!


Solution

  • Concepts

    JSONPath apparently has a filter syntax that allows you to insert arbitrary Javascript into an expression for the purposes of matching or filtering. It also uses @ as a shortcut for the current node. Their example of combining these two things looks like this:

    $..book[?(@.price<10)] // filter all books cheapier than 10

    So this is probably what you want to use here.

    Solution

    To test the query I had in mind, I modified the jsonpath-test-js.html file in JSONPath's repo to test your data. You can copy-paste my sample to an HTML file and just load it in a browser.

    Their test suite has an array of objects with fields called o and p. o contains the original data to operate on while p contains an array of JSONPath expressions to apply to o. It loops over all these pairs and applies all the ps to their respective os, printing out the result. Not as handy as a simple REPL, but it'll do.

    Here's what I came up with:

    <html>
    <head>
    <title> JSONPath - Tests (js)</title>
    <script type="text/javascript" src="http://www.json.org/json.js"></script>
    <script type="text/javascript"
            src="http://jsonpath.googlecode.com/svn/trunk/src/js/jsonpath.js">
    </script>
    </head>
    <body>
    <pre>
    <script type="text/javascript">
    var out = "", tests = 
    [ { "o": { "results" : {  "div" : [  {  "clazz": "sylEntry",
               "id": "sylOne",  "div": [  {  "clazz": "sT",  "id": "sOT",
               "p": "Mon 11/17, Computer work time"  },  {  "clazz": "des",
               "id": "dOne",  "p": "All classes Siebel 0218"  }  ]  }  ]  }  },
        "p": ["$.results..div[?(@.clazz=='sT')].p", // my suggestion expression
              "$.results..div[*].p"]},             // your question's expression
    ];
    
    function evaluate($, p) {
      var res = eval(p);
      return res != null ? res.toJSONString() : null;
    }
    
    for (var i=0; i<tests.length; i++) {
       var pathes;
       for (var j=0; j<tests[i].p.length; j++) {
          pre = ">";
          if (pathes = jsonPath(tests[i].o, tests[i].p[j], {resultType: "PATH"}))
             for (var k=0; k<pathes.length; k++) {
                out += pre + " " + pathes[k] +
                       " = " + evaluate(tests[i].o, pathes[k]) + "\n";
                pre = " ";
             }
          }
          out += "<hr/>";
       }
       document.write(out);
      </script>
    </pre>
    </body>
    </html>
    

    Note that this will first print the results of my query expression and then print the results of yours, so we can compare what they produce.

    Here's the output it produces:

    > $['results']['div'][0]['div'][0]['p'] = "Mon 11/17, Computer work time"
    > $['results']['div'][0]['div'][0]['p'] = "Mon 11/17, Computer work time"
      $['results']['div'][0]['div'][4]['p'] = "All classes Siebel 0218"
    

    So the correct operator in the filter expression is ==, meaning the correct expression for you is:

    $.results..div[?(@.class=='sT')].p
    

    However, I discovered one unfortunate issue (at least in the Javascript implementation of JSONPath): using the word 'class' in the above query results in this:

    SyntaxError: jsonPath: Parse error: _v.class=='sT'
    

    My only guess is that there's an eval being called somewhere to actually evaluate the JSONPath expression. class is a reserved word in Javascript, so it's causing issues. Let's try using the alternate syntax for @.class:

    $.results..div[?(@.['class']=='sT')].p
    

    Results:

    > $['results']['div'][0]['div'][0]['p'] = "Mon 11/17, Computer work time"
    > $['results']['div'][0]['div'][0]['p'] = "Mon 11/17, Computer work time"
      $['results']['div'][0]['div'][5]['p'] = "All classes Siebel 0218"
    

    So use the above expression and you should be good to go! The filter feature looks powerful, so it'll probably be well worth exploring its capabilities!