JSONQuery/JSONPath support is based on the
specification for JSONPath and has been greatly expanded
with numerous additional features for more powerful querying capabilities. This expanded set of functionality is
called JSONQuery.
A JSONQuery starts with object URL that is indicates the list being queried. JSONQuery can then be used to
find objects by different property values using various operators. For example:
/Customer/?lastName='Smith'
This will find all the customers that have a last name of "Smith". We could do more sophisticated queries as well:
/Product/?price<15.00 & rating>3
This will find all the products with a price less than 15.00 and a rating more than 3. You can enclose queries
in brackets to perform successive operations:
/Product/[?price<15.00][0:10]
This will return the first ten products that have a price less than 15.00. JSONQuery can also extract properties from
a list of objects to return a set of property values:
/Product/[?price<15.00][=name]
This will return a list of the names of the products less than 15.00. This is called mappings, and you can
do more sophisticated mappings as well:
/Customer/[={name:firstName + ' ' + lastName, address: street + state}]
This will return a list of objects that where the name is the first name plus the last name, and the
address is the street plus the state.
We can also sort results:
/Product/[\rating]
This would return a list of products sorted in descending order by their rating. [/expr] indicates ascending
and [\expr] indicates descending. You can also use multiple sort orders:
/Product/[\rating, /price]
This would sort by descending order for rating, and ties would be sorted by ascending order by price.
Another capability of JSONQuery is the recursive decent operator, “..”. One can
recursively search data with the recursive operator. For example the
query:
/Customer/objectid..name
This 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.
You can search for property values that match a specific object by using the object id literal syntax:
/Friend/?bestFriend=/Friend/33
We can also search for matches within an array using the contains method:
/PurchaseOrder/?productsPurchased.contains(/Product/21)
The full set of operators that can be used in JSONQuery expressions are:
| Operator |
Meaning |
| .property |
This will return the provided property of the object, behaving exactly
like JavaScript.
|
| [expression] |
This returns the property name/index defined by the evaluation of
the provided expression, behaving exactly like JavaScript.
|
| [?expression] |
This will perform a filter operation on an array, returning all the
items in an array that match the provided expression. This operator does not
need to be in brackets, you can simply use ?expression, but since it does not
have any containment, no operators can be used afterwards when used
without brackets.
|
| [/expression], [\expression], [/expression, /expression] |
This performs a sort
operation on an array, with sort based on the provide expression. Multiple comma delimited sort
expressions can be provided for multiple sort orders (first being highest priority).
|
| [=expression] |
This performs a map operation on an array, creating a new array
with each item being the evaluation of the expression for each item in the source array.
|
| [start:end:step] |
This performs an array slice/range operation, returning the elements
from the optional start index to the optional end index, stepping by the optional step number.
|
| [expr,expr] |
This a union operator, returning an array of all the property/index values from
the evaluation of the comma delimited expressions.
|
| .* or [*] |
This returns the values of all the properties of the current object.
|
| $ |
This is the root object, If a JSONQuery expression does not being with a $,
it will be auto-inserted at the beginning.
|
| @ |
This is the current object in filter, sort, and map expressions. This is generally
not necessary, names are auto-converted to property references of the current object
in expressions.
|
| ..property |
Performs a recursive search for the given property name, returning
an array of all values with such a property name in the current object and any subobjects
|
| expr = expr |
Performs a comparison (like JS's ==). When comparing to
a string, the comparison string may contain wildcards * (matches any number of
characters) and ? (matches any single character).
|
| +, -, /, *, &, |, %, (, ), <, >, <=, >=, != |
These operators behave just as they do
in JavaScript.
|
| /Table/id |
This is an object id literal, and this represents the object with the given id, and can be used to match on that object in expressions
|
date(<ISO date string>) or date(<epoch milliseconds>)
|
This represents a date and can be used to match and compare dates in expressions. For example:
?born=date("Sat, 07 Feb 2009 22:51:26 GMT") or ?created=date(1234047109376)
|
| .method(arguments) |
Performs a method call. You can add new methods to objects and arrays to have them be available for execution from queries, but they must have their safe attribute set to true in their method definition.
|
.method(?expr)
|
By using ?expr as a parameter to a method call, a function is passed to method where the function returns the result of the provided JSONQuery expression. For example:
/Table/3.someMethod(?retailPrice-wholesalePrice)
This would provide a function that calculates the expression (using the properties of the array item) for each array item.
|
| .distinct() |
Removes duplicates from the current result set
|
.contains(values...)
|
Returns true if one the values in the arguments matches on of the values in the array. Expressions can also be passed to this method, and if the expression returns true for any of the array values, than this method returns true.
|
| .sum(?expr) |
Calculates the sum of the expression computed for each object in the array. For example, this would calculate the sum of the values of the quantity property of all the Product instances:
/Product/.sum(?quantity)
|
.max(?expr)
|
Calculates the maximum of the expression computed for each object in the array. |
.min(?expr)
|
Calculates the minimum of the expression computed for each object in the array.
|
| .length |
Returns the total number of items in the result set
|
| RegExp("regex").test(expr) |
Matches on the regular expression. For example to find all the author names that start with "J":
/Book/[?RegExp('J.*').test(authorName)]
|
Remember to always properly encode your JSONQuery queries when using them in URLs,
leaving queries unencoded is a very common mistake.
Note that it is recommended that you use the HTTP Range header
for paging rather than the slice operator.
JSONQuery uses 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:
&, |, =, !=, >=, <=, +, -, /, *, ?, :
Limiting Querying
All queries are executed through the "query" method on the containing class. The default implementation of querying is in Object.query, and all
classes will inherit this query method unless they override it. The
implementation of Object.query is very simple, and the actual
complexity on parsing queries is handled elsewhere:
query: function(query, target){
return query.execute(target);
}
The query function is passed two arguments. The first is the query
string (a special type of string), and the second is the target that is
being queried (typically the table/set of instances of the class). The
query string is an instance of (instanceof) QueryString, and is a
subclass String. A QueryString includes an execute() method that
executes the query string through JSONquery parsing (to JavaScript).
However, it is also features some special regex extensions to make it
easier to work with.
The QueryString class, which is the type for the query parameter, includes several special
expressions that can be used in regex to limit query forms. For
example, to limit a query to simple name-value filtering, you could do:
query: function(query, target){
if (query.match(/^\?$prop=$value$/) {
return query.execute(target);
}
throw new AccessError("Query to complex, not allowed");
}
There are several of these special regex expressions for use in
queries. Each of them is a captured group (and returns what it matches,
unless otherwise noted):
$prop - Matches a property name. In ?foo=, it would match foo. In ?@.foo=, it would match @.foo, and return foo.
$value - Maches a value. In ?foo='bar', it would match 'bar', and ?foo=44, it would match 44
$comparator - Matches a comparison operator, including =, >, <, <=, >=, !=.
$operator - Matches a value operator, including +, -, *, /, &, |, ?, :
$logic - Matches a logic operator, including &, |
$expression - Matches an expression (within a filter or sort
operation). In [?foo='bar'&price<34], it would match
foo='bar'&price<34
$filter - Matchs a filter and returns the expression in it. In
[?foo='bar'&price<34], it would match the whole string, and
returns the expression
$sort - Matches a sort operation
Class({
id:"MyClass",
query: function(query, target){
if (query.match(/^\?$prop=$value$/) {
return query.execute(target);
}
throw new AccessError("Query to complex, not allowed");
},
...
One could also augment a query to add your own internal constraints:
query: function(query, target){
if (query.match(/^\?$prop=$value$/) {
return (new QueryString(query + "&active=true")).execute(target);
}
throw new AccessError("Query to complex, not allowed");
},