This tutorial introduces the JSONiq language, which declaratively manipulates JSON data.
Why don't you go ahead can try the queries of this document on our online demo interface?
http://jsoniq.zorba-xquery.com/
As explained on http://www.json.org/, JSON is a lightweight data-interchange format designed for humans as well as for computers. It supports as values:
JSONiq extends XQuery to query and update JSON data, like XML data.
Here is an appetizer before we start the tutorial from scratch.
let $stores :=
[
{ "store number" : 1, "state" : "MA" },
{ "store number" : 2, "state" : "MA" },
{ "store number" : 3, "state" : "CA" },
{ "store number" : 4, "state" : "CA" }
]
let $sales := [
{ "product" : "broiler", "store number" : 1, "quantity" : 20 },
{ "product" : "toaster", "store number" : 2, "quantity" : 100 },
{ "product" : "toaster", "store number" : 2, "quantity" : 50 },
{ "product" : "toaster", "store number" : 3, "quantity" : 50 },
{ "product" : "blender", "store number" : 3, "quantity" : 100 },
{ "product" : "blender", "store number" : 3, "quantity" : 150 },
{ "product" : "socks", "store number" : 1, "quantity" : 500 },
{ "product" : "socks", "store number" : 2, "quantity" : 10 },
{ "product" : "shirt", "store number" : 3, "quantity" : 10 }
]
let $join :=
for $store in jn:values($stores), $sale in jn:values($sales)
where $store("store number") = $sale("store number")
return {
"nb" : $store("store number"),
"state" : $store("state"),
"sold" : $sale("product")
}
return [$join]
[
{ "nb" : 1, "state" : "MA", "sold" : "broiler" },
{ "nb" : 1, "state" : "MA", "sold" : "socks" },
{ "nb" : 2, "state" : "MA", "sold" : "toaster" },
{ "nb" : 2, "state" : "MA", "sold" : "toaster" },
{ "nb" : 2, "state" : "MA", "sold" : "socks" },
{ "nb" : 3, "state" : "CA", "sold" : "toaster" },
{ "nb" : 3, "state" : "CA", "sold" : "blender" },
{ "nb" : 3, "state" : "CA", "sold" : "blender" },
{ "nb" : 3, "state" : "CA", "sold" : "shirt" }
]
JSONiq maps JSON types to XQuery. Numbers are xs:integer or xs:decimal, strings are xs:string, true and false are xs:boolean and null is a new atomic type jn:null.
JSONiq introduces new items: objects, arrays and pairs. Objects have pairs. Pairs have a string name and a value. Array have members which are values. Values are objects, arrays, XML nodes or atomic items.
The new item types for objects and arrays are object() and array(). json-item() is a supertype of both, and json-pair() is the item type for pairs.
JSONiq introduces JSON constructors, in a similar way to XML constructors.
You can put any expression in a array. The items in the sequence produced by the expression will become members of the array:
[ 1 to 10 ]
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
Or you can dynamically compute an object:
{
"Greeting" : (let $d := "Mister Spock"
return concat("Hello, ", $d)),
"Farewell" : string-join(("Live", "long", "and", "prosper"),
" ")
}
{ "Greeting" : "Hello, Mister Spock", "Farewell" : "Live long and prosper" }
You can also dynamically generate pairs:
concat("Integer ", 2) : 2 * 2
"Integer 2" : 4
and then wrap lots of them in an object:
{
for $i in 1 to 10
return concat("Square of ", $i) : $i * $i
}
{
"Square of 1" : 1,
"Square of 2" : 4,
"Square of 3" : 9,
"Square of 4" : 16,
"Square of 5" : 25,
"Square of 6" : 36,
"Square of 7" : 49,
"Square of 8" : 64,
"Square of 9" : 81,
"Square of 10" : 100
}
Every well-formed JSON document is a JSONiq query as well. This means that you can copy-and-paste a JSON document into a query. The following are JSONiq queries that are "idempotent" (they just output themselves):
{ "pi" : 3.14, "sq2" : 1.4 }
{ "pi" : 3.14, "sq2" : 1.4 }
[ 2, 3, 5, 7, 11, 13 ]
[ 2, 3, 5, 7, 11, 13 ]
{
"operations" : [
{ "binary" : [ "and", "or"] },
{ "unary" : ["not"] }
],
"bits" : [
0, 1
]
}
{
"operations" : [
{ "binary" : [ "and", "or" ] },
{ "unary" : [ "not" ] }
],
"bits" : [
0, 1
]
}
This works with objects, arrays (even nested), strings, numbers, booleans, null. The exceptions to this rule (but we are working on it!) are that:
(i) if a pair has a true, false or null value, there must be a space on at least one side of the colon (this will be fixed).
(ii) empty objects are not recognized
(iii) characters escaped with the \ in JSON strings are not recognized
It also works the other way round: if your query outputs an object or an array, you can readily use it as a JSON document.
If you have some JSON data, you can access it and navigate.
All you need to know is, again: JSONiq views
If you use an object as a functor and provide it with a string, it will return the pair named after its parameter:
let $person := {
"first name" : "Sarah",
"age" : 13,
"gender" : "female",
"friends" : [ "Jim", "Mary", "Jennifer"]
}
return $person("first name")
"first name" : "Sarah"
If you use an array as a functor and provide it with an integer, it will return the corresponding entry:
let $friends := [ "Jim", "Mary", "Jennifer"]
return $friends(2)
Mary
Given a pair, you can get its name and value using the functions jn:name and jn:value:
let $pair := "name of the pair" : "value of the pair"
return { "name" : jn:name($pair), "value" : jn:value($pair) }
{ "name" : "name of the pair", "value" : "value of the pair" }
This functions also exist in plural form for objects:
let $person := {
"name" : "Sarah",
"age" : 13,
"gender" : "female",
"friends" : [ "Jim", "Mary", "Jennifer"]
}
return { "names" : [ jn:names($person)],
"values" : [ jn:values($person)]
}
{
"names" : [ "name", "age", "gender", "friends" ],
"values" : [ "Sarah", 13, "female", [ "Jim", "Mary", "Jennifer" ] ]
}
jn:values can also be used for an array:
let $person := {
"name" : "Sarah",
"age" : 13,
"gender" : "female",
"friends" : [ "Jim", "Mary", "Jennifer"]
}
return jn:values($person("friends"))
[ "Jim", "Mary", "Jennifer" ]
Remember last century's SELECT FROM WHERE statements? Well, JSONiq didn't throw out the baby with the bathwater. It has selection, projection and join capability.
let $stores :=
[
{ "store number" : 1, "state" : "MA" },
{ "store number" : 2, "state" : "MA" },
{ "store number" : 3, "state" : "CA" },
{ "store number" : 4, "state" : "CA" }
]
let $sales := [
{ "product" : "broiler", "store number" : 1, "quantity" : 20 },
{ "product" : "toaster", "store number" : 2, "quantity" : 100 },
{ "product" : "toaster", "store number" : 2, "quantity" : 50 },
{ "product" : "toaster", "store number" : 3, "quantity" : 50 },
{ "product" : "blender", "store number" : 3, "quantity" : 100 },
{ "product" : "blender", "store number" : 3, "quantity" : 150 },
{ "product" : "socks", "store number" : 1, "quantity" : 500 },
{ "product" : "socks", "store number" : 2, "quantity" : 10 },
{ "product" : "shirt", "store number" : 3, "quantity" : 10 }
]
let $join :=
for $store in jn:values($stores), $sale in jn:values($sales)
where $store("store number") = $sale("store number")
return {
"nb" : $store("store number"),
"state" : $store("state"),
"sold" : $sale("product")
}
return [$join]
[
{ "nb" : 1, "state" : "MA", "sold" : "broiler" },
{ "nb" : 1, "state" : "MA", "sold" : "socks" },
{ "nb" : 2, "state" : "MA", "sold" : "toaster" },
{ "nb" : 2, "state" : "MA", "sold" : "toaster" },
{ "nb" : 2, "state" : "MA", "sold" : "socks" },
{ "nb" : 3, "state" : "CA", "sold" : "toaster" },
{ "nb" : 3, "state" : "CA", "sold" : "blender" },
{ "nb" : 3, "state" : "CA", "sold" : "blender" },
{ "nb" : 3, "state" : "CA", "sold" : "shirt" }
]
Our implementation supports collections of JSON objects or arrays:
dml:collection("my:data")
{ "foo" : "Your" }
{ "foo" : "Collection" }
{ "foo" : "of" }
{ "foo" : "JSON" }
{ "foo" : "objects" }
It is also possible to get JSON content with an HTTP request, or by parsing it from a string. Functions that handle this are available.
You can readily use XML and JSON in the same program. If you put a JSON array in an XML constructor, it will be flattened (its member values are recursively taken, even in arrays of arrays). If you put a JSON pair in an XML constructor, it will be unboxed (its value is taken).
let $data := {
"color" : "blue",
"closed" : true,
"points" : [[10,10], [20,10], [20,20], [10,20]]
}
let $stroke := attribute stroke { $data("color") }
let $points := attribute points { $data("points") }
return
if ($data("closed")) then
<svg><polygon>{ $stroke, $points }</polygon></svg>
else
<svg><polyline>{ $stroke, $points }</polyline></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg><polygon stroke="blue" points="10 10 20 10 20 20 10 20"/></svg>
JSONiq supports JSON updates. You can declaratively update your JSON data. JSONiq provides functions that produce a list of updates. The list of updates that is eventually output by your program is then applied to your JSON data.
let $my-data := jn:json("persons.json")
let $john := jn:value($my-data("John"))
let $mary := jn:value($my-data("Mary"))
return (jn:replace-value($john, "status", "married"),
j:replace-value($mary, "status", "married"))
JSONiq works with the XQuery 3.0 standard (switch, typeswitch and try-catch expressions, universal/existential quantifiers, path expressions, filtering expressions, functors, mappings, grouping, windowing will work). The Zorba implementation is also compatible with the proprietary Zorba scripting.