JSONiq for XQuery users

JSON for XQuery


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/


JSON

Elevator Pitch

And here you go

JSONiq types

JSON Constructors

JSON as a subset of JSONiq

JSON Navigation

Objects

Arrays

Pairs

Relational Algebra

Access external data

I want more

JSON

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:

  1. objects (string-to-value map)
  2. arrays (ordered sequence of values)
  3. strings
  4. numbers
  5. booleans (true, false)
  6. null

JSONiq extends XQuery to query and update JSON data, like XML data.

Elevator Pitch

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" }

 ]

And here you go

JSONiq types

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.

JSON Constructors

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

}

JSON as a subset of JSONiq

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.

JSON Navigation

If you have some JSON data, you can access it and navigate.

All you need to know is, again: JSONiq views

  1. an array as a sequence of values,
  2. an object as a set of pairs,
  3. a pair as a (name, value) tuple.

Objects

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"

Arrays

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

Pairs

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" ]

Relational Algebra

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" }

 ]

Access external data

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.

JSON and XML

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>

I want more

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.