Search code examples
jsonxquery

Is XQuery 3.1 designed for advanced JSON editing?


XQuery 3.1 introduced several JSON functions. I was wondering if these functions were designed with advanced JSON editing in mind.

As far as I can tell, these functions only work for simple JSONs, like for instance...

let $json:={"a":1,"b":2} return map:put($json,"c",3)
{
  "a": 1,
  "b": 2,
  "c": 3
}

and

let $json:={"a":1,"b":2,"c":3} return map:remove($json,"c")
{
  "a": 1,
  "b": 2
}

The moment the JSON gets a bit more complex:

let $json:={"a":{"x":1,"y":2},"b":2} return map:put($json?a,"z",3)
{
  "x": 1,
  "y": 2,
  "z": 3
}

let $json:={"a":{"x":1,"y":2,"z":3},"b":2} return map:remove($json?a,"z")
{
  "x": 1,
  "y": 2
}

Obviously map:put() and map:remove() do exactly what you tell them to do; select the "a"-object and add or remove an attribute.
However, when I want to edit a JSON document, I'd like to edit the entire document. And as far as I know that's not possible with the current implementation. Or is it? At least something like map:put($json,$json?a?z,3) or map:remove($json,$json?a?z) doesn't work.

For the removal of the "z"-attribute I did come up with a custom recursive function (which only works in this particular use-case)...

declare function local:remove($map,$key){
  if ($map instance of object()) then
    map:merge(
      map:keys($map)[.!=$key] ! map:entry(.,local:remove($map(.),$key))
    )
  else
    $map
};
let $json:={"a":{"x":1,"y":2,"z":3},"b":2} return
local:remove($json,"z")

...with the expected output...

{
  "a": {
    "x": 1,
    "y": 2
  },
  "b": 2
}

...but I wasn't able to create a custom "add"-function.

I imagine advanced JSON editing can be done with some pretty advanced custom functions, but instead I would very much like to see that something like map:put($json,$json?a?z,3) would work, or otherwise an extra option which lets map:put() put out the entire JSON document, like map:put($json?a?z,3, <extra-option> ).

Or... I'd have to settle with the notion that XQuery isn't the right choice of course.


Solution

  • You're correct that doing what I call deep update of a map is quite difficult with XQuery 3.1 (and indeed XSLT 3.0) as currently defined. And it's not easy to define language constructs with clean semantics. I attempted to design a construct as an XSLT extension instruction - see https://saxonica.com/documentation10/index.html#!extensions/instructions/deep-update -- but I don't think its anywhere near a perfect solution.