JNodes

JNodes (think JSON Nodes) encapsulate the values found in a tree of maps and arrays, such as the tree formed by parsing a JSON text. JNodes can be used in path expressions in the same way as XML nodes (now called XNodes when a distinction is needed) are used in a tree formed by parsing XML.

Many operations apply both to JNodes and XNodes, so the term GNode is introduced to refer to both kinds generically.

Consider the tree formed by applying the parse-json function to a (recursive) JSON text as follows:

let $tree := parse-json('{ "type": "package", "name": "org", "content": [ { "type": "package", "name": "xml", "content": [ { "type": "package", "name": "sax", "content": [ { "type": "class", "name": "Attributes"}, { "type": "class", "name": "ContentHandler"}, { "type": "class", "name": "XMLReader"} ] }] }] }')

The value of $tree is a map, and this can be wrapped in a JNode either explicitly by a call on the function jtree($tree), or implicitly by using $tree as the left hand operand of the / operator.

As a simple example, $tree/type (which is short for $tree/child::type) returns a JNode that wraps the top-level map entry "type":"package". The important properties of this JNode are its key ("type") and its value ("package"): these properties are accessible using the functions jkey and jvalue.

It is also possible to navigate from a JNode to its descendants: the expression $tree//name returns six JNodes, whose value properties are respectively "org", "xml", "sax", "Attributes", "ContentHandler", and "XMLReader". The path expression $tree//name[../type="class"]/jvalue() returns the sequence ("Attributes", "ContentHandler", "XMLReader"). This illustrates that it is possible to navigate from a JNode to its parent using the parent axis, here abbreviated in the usual way to ... The path expression can be written in full as:

jtree($tree)/descendant-or-self::*/child::name[parent::*/child::type="class"]/jvalue()

All the axes available for XNodes are also available for JNodes (including four new axes introduced in XPath 4.0: following-or-self, preceding-or-self, following-sibling-or-self, and preceding-sibling-or-self). This works only because maps in 4.0 are ordered: in the above example, the preceding-sibling axis starting at a JNode whose key is "name" is in each case a JNode whose key is "type".

When selecting the children of a JNode that wraps an array, the child JNodes again have a key and a value property: this time, the key property is an integer giving the one-based index of an array member, and the value property is the value of the array member. So the expression $tree//name[.="ContentHandler"]/../jkey() returns 2, because the map whose name entry is "ContentHandler" is the second member of the containing array.

The node tests used to select chosen nodes on an axis differ slightly from those used with XNodes. The most common node tests are:

  • * selects all the entries in a map, or all the members of an array.

  • name selects the entry in a map whose key is the string name, which must take the form of an NCName.

  • get("key value") selects the entry in a map whose key is "key value". In this case the key can be any atomic item (including, as in this example, a string containing spaces). When selecting members of an array, get(3) selects the third member.

  • ~type selects JNodes whose value is of a given type. For example child::~xs:integer selects child JNodes whose value is an integer, while child::~array() selects child JNodes whose value is an array. Using record types is often convenient: in the above example, $tree//~record(type, name) selects all descendant maps having entries with keys "type" and "name".

JNodes are intended primarily for use with trees of maps and arrays derived from JSON, in which the values of entries/members are typically single items. They can also be used with maps and arrays whose values are multi-item sequences, but their use in such cases is slightly less convenient. For details consult the XPath 4.0 specification. It's entirely possible that the specification in this area will change over time.