We recently added JSONPath support to the Dojo Toolkit. In the Dojo Toolkit 1.1, JSONPath queries can now be executed by calling dojox.jsonPath.query. JSONPath is a powerful tool for extracting data from JavaScript data objects, and has query capabilities that are similar to those of XPath queries on XML, except applied to JSON originating data. JSONPath follows JavaScript/C syntax where possible, borrowing from ES4 and Python as needed for certain operations, making for a very readable portable syntax.
A JSONPath query can be called with the following call:
dojox.jsonPath.query(object,query,options)
Where the object
is the object to queried, the query is the JSONPath query to execute, and the optional options parameter provides options for changing evaluation.
There are a number of common patterns that can be simplified by JSONPath. While it is worthwhile to view the Stefan Goessner’s specification for JSONPath, we will look at a few examples that demonstrate the capabilities. A simple JSONPath expression starts with “$” symbol to indicate a reference to the object being queried. JSONPath is particularly useful for array operations. One capability of JSONPath is to iterate through all items in an array and return property values as a result. For example if you had an array of objects that each had a foo property, you can extract all the foo property values into an array with:
var results = dojox.jsonPath.query([{foo:"a"},{foo:"b"}],"$.foo");
results == ["a","b"]
Another capability of JSONPath is the recursive decent operator, “..”. One can easily recursively search data with the recursive operator. For example the query "$..name"
, would search through all the children of the provided object, as well as the sub children and so on for the name
properties and return their values.
The full set of operators that can be used in JSONPath expressions are:
Operator | Meaning |
$ | The Root Object |
@ | The current object/element |
. or [] | The child operator |
.. | Recursive descent |
* | All objects |
[] | Subscript operator |
[,] | Union operator |
[start:end:step] | Array slice operator |
?() | Applies a filter sub-expression to extract all items from an array that match the expression |
() | Sub-expression |
Sophisticated expressions can be used for queries based on these operators. For example, to search through a list of items for the names of all the products with price less than 15 and rating above 2, we could use the query:
$[?(@.price < 15 & @.rating > 2)].name
JSONPath can be very portable, and has significant potential for not only client side use, but as a means of querying data on a server. Stefan Goessner has written a PHP-based storage system that accepts JSONPath queries, and Persevere uses JSONPath for querying its database with simple Ajax REST requests. This provides the opportunity for the same query language to be used with consistency on both client and server. JSONPath is a safer query language than SQL, which is difficult to safely subset and use across the wire. SQL is also heavy to implement on the client (except for tiny subsets of SQL), while JSONPath on the other hand has a very compact implementation (~4KB). SQL is also a conceptual mismatch for JSON, requiring many Object Query Language extensions to even be usable.
Result-Based Evaluation
We have based the Dojo Toolkit implementation on Stefan’s implementation. However, we have also fixed several defects (proper handling of special characters in properties) and added an alternate evaluation technique. We also introduced the concept of result-based evaluation in addition to Stefan’s item-based evaluation. Result-based evaluation follows general JavaScript evaluation (where possible) much more closely than item-based evaluation, which always attempts to return an array for valid results. Any JavaScript expression that evaluates to a defined value (not undefined) and is also a valid JSONPath expression will have identical results in result-based JSONPath evaluation as it would in JavaScript.
Result-based evaluation also allows successive narrowing operations to be performed on an array. The evaluation technique does not affect the query syntax. Result-based evaluation performs each operation on the result of the last operation. Item-based evaluation performs each operation on each item from the last result set. The following table compares some examples of result-based and item-based evaluation. For these examples we will assume that we are querying this object:
{
name:"My Object",
numbers:[2,4,6,8,10,12,14,16,18,20]
}
Query | Item-based | Result-based |
$.name | [“My Object”] | “My Object” |
$.numbers[1] | [4] | 4 |
$.numbers[?(@>10)][0:3] | false | [12,14,16] |
Currently, the existing implementation defaults to item-based evaluation. To use result-based evaluation, you can specify a result base evaluation in the options parameter:
dojox.jsonPath.query(object, "$.numbers[1]",{evalType:"RESULT"})
Safe-Evaluation
The original JSONPath did not provide any stipulation about the extent of allowable sub-expressions, instead just stating that sub-expressions could be anything that the underlying script engine can handle. However, this is not a portable solution (not all implementations have a JavaScript VM available), and it is also not safe since it allows for the arbitrary execution of code. Therefore we have defined a safe subset of the sub-expressions which is highly portable and is safe from arbitrary code execution. The following operators (separated by commas) may be used in sub-expressions with safe-evaluation enabled:
&, |, =, !=, >=, <=, +, -, /, *, ?, :
Safe-evaluation can be enabled setting safeEval to true in the options argument. For example:
dojox.jsonPath.query(object, "$.numbers[1]",{safeEval:true})
When safeEval is enabled and unsafe operators are used, an error will be thrown. All the examples listed above will behave exactly the same with safe-evaluation enabled.
This JSONPath implementation can also be downloaded without Dojo packaging.