myPatterns/JS + JSONQuery

JSONQuery is a mini-language for writing queries over native JavaScript data such as arrays and objects. JSONQuery is part of Dojo starting from version 1.2, and provides a comprehensive set of data querying tools including filtering, recursive search, sorting, mapping, range selection, and flexible expressions with wildcard string comparisons and various operators.

myPatterns/JS can be integrated with JSONQuery, as shown by a prototype called myJSONQuery/JS, demonstrated below with a few examples.

pattern matching operators

myJSONQuery/JS adds just two pattern matching operators to JSONQuery:

using the ~? operator

One of the most common uses of JSONQuery is recursively searching for objects of a given shape within an array. For instance, the following JSONQuery selects the (possibly nested) objects having fields a and b of values 1 and 2:
var arr1 = [{a:{a:1, b:2, c:1}, b:2, c:2}, {a:1, b:2, c:3}, {a:1, b:2}];
JSONQuery('..[?(a=1 & b=2)]', arr1);

The same query can be written in myJSONQuery using the ~? operator with the JSON pattern "{a:1, b:2}", instead of the ? operator with the equivalent boolean expression (a=1 & b=2):

JSONQuery('..[~?"{a:1,b:2}"]', arr1);

In this case, the two queries are very similar, so using patterns or predicates is really a matter of personal taste.

However, consider the following pattern query, selecting all the elements containing a field b with value 2 and fields a and c having any value:

JSONQuery('..[~?"{a:%x,b:2,c:%y}"]', arr1);

(The printed form includes some references because the structure {a:1, b:2, c:1} occurs both as a sub-data embedded in the first top-level value and as a top-level value in the selection: the second value.)

At first sight, the equivalent non-pattern query is shorter than that:

JSONQuery('..[?(b=2)]', arr1);

Ooops! As we can see from the different result, the above JSONQuery is not strictly equivalent to the previous pattern query, as it also selects objects having field c undefined! The strictly equivalent JSONQuery is in fact the following:

JSONQuery('..[?(a!=undefined & b=2 & c!=undefined)]', arr1);
which is already longer and less elegant than the pattern-based one above.


using the ~= operator

Things get even more interesting when using the ~= operator to extract data based on a pattern.

For example, the following pattern query selects the same objects as above, but extracts from each the value of the fields a and c:

JSONQuery('..[~="{a:%x,b:2,c:%y}"]', arr1);

In this case, the equivalent JSONQuery without patterns is considerably longer, because the JSON pattern both filters some structures and extracts some sub-data thereof. These two steps have to be explicited in the pure JSONQuery:

JSONQuery('..[?(a!=undefined & b=2 & c!=undefined)][={x:@.a, y:@.c}]', arr1);

As can be seen, pattern matching queries in myJSONQuery can be simpler (or even significantly simpler) than their equivalent non-pattern forms, because a JSON pattern may combine selection and extraction operations.

matching arrays

Another useful feature of JSON patterns is that they can also match arrays themselves (not only structures contained in arrays).

Consider for instance the following recursive array, and the following pattern query extracting all its non-empty sub-arrays (the "[%x|%y]" notation means an array whose first element is x and whose rest is y):

var arr2 = [[1], 2, [3,[4,5]]];
JSONQuery('[~?"[%x|%y]"]', arr2);

The equivalent non-pattern query is as follows:

JSONQuery('[?(@[0]!=undefined)]', arr2);

As can be seen, the pattern form is slightly more concise and more readable.

But, as in the previous section, patterns become more interesting when used to extract sub-data, like in the following pattern query:

JSONQuery('[~="[%x|%y]"]', arr2);

In this case, the equivalent non-pattern query is considerably longer:

JSONQuery('[?(@[0]!=undefined)][={x:@[0], y:@.slice(1)}]', arr2);

queries in custom notations

One of the most powerful features of myPatterns/JS is that it goes beyond JSON patterns, by letting you to define your own pattern notations. Let us see how this feature also integrates with JSONQuery, by considering the X Windows notation for a window geometry as a rectangle on the screen, noted as: [width x height +xpos +ypos].

This custom notation can easily be defined in myPatterns/JS as follows :

// definition of a Rectangle object
function Rectangle(x, y, w, h)
  this.xpos = x;
  this.ypos = y;
  this.width = w;
  this.height = h;

// definition of its custom notation:
Rectangle.prototype.matches = function(pat, off, acc) { 
  off = matchToken("[", pat, off);
  off = matchData(this.width, pat, off, acc);
  off = matchToken("x", pat, off);
  off = matchData(this.height, pat, off, acc);
  off = matchToken("+", pat, off);
  off = matchData(this.xpos, pat, off, acc);
  off = matchToken("+", pat, off);
  off = matchData(this.ypos, pat, off, acc);
  return matchToken("]", pat, off);
That's all! Now we are able to write pattern queries over an array of window objects such as the following two queries, which select from the array below all the windows of size 300x200 pixels, and respectively extract some data thereof:
var windows = [
  new Rectangle(100, 50, 300, 200),
  new Rectangle(200, 350, 300, 200),
  new Rectangle(400, 650, 300, 200),
  new Rectangle(600, 50, 200, 100)

JSONQuery('[~?"[300x200+%w+%h]"]', windows);

JSONQuery('[~="[300x200+%w+%h]"]', windows);

combining patterns with other operators

Of course, JSON patterns or even custom patterns can be freely combined with any other operators in JSONQuery. For instance, the following query selects the same windows as above, but further filters the ones higher than 100 pixels, and sorts out the result, first on decreasing heigth, then on increasing width:
JSONQuery('[~="[300x200+%w+%h]"][?(h>100)][\\h,/w]', windows);