Is XQuery 3.1 designed for advanced JSON editing?

Viewed 245

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.

4 Answers

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.

I wanted the same thing, so wrote my own surrogate XSLT functions, tan:map-put() and tan:map-remove(), which do deep map replacement and removal: https://github.com/Arithmeticus/XML-Pantry/tree/master/maps-and-arrays

These can be incorporated in an XSLT workflow via xsl:include or xsl:import, or in an XQuery one via fn:transform(). Some of the other functions may be useful, too. If these functions don't do exactly what you want, they might catalyze your own variation.

In XQuery 3.1, you are supposed to write a recursive function for such things. You could put all you functions in a module file, and then load the module when you need them...

Besides that, Xidel has an object editing extension from before JSONiq and XPath 3.1. For a global mutable variable (without let), you can write:

$json:={"a":{"x":1,"y":2,"z":3},"b":2},
(($json).a).z := 4   


$json:={"a":{"x":1,"y":2,"z":3},"b":2},
$json("a")("z") := 4

#comment119588453_67652693 by @ChristianGrün:

If updates are required, we tend to convert JSON data to XML.

I'm a user and last week (with a little help from @BeniBela) I've had a look at whether this could be done with json-to-xml(), Xidel's own x-replace-nodes() and xml-to-json(). The answer is yes. Thanks for the hint.

For reference and for anyone interested, here's 1 example.

To change key "c" in {"x":{"a":1,"b":2,"c":3},"y":2} to "d":

$ xidel -s '{"x":{"a":1,"b":2,"c":3},"y":2}' -e '
  xml-to-json(
    x:replace-nodes(
      json-to-xml(
        serialize($json,{"method":"json"})
      )//fn:map[@key="x"]/fn:number[@key="c"]/@key,
      attribute key {"d"}
    )
  )
'
{"x":{"a":1,"b":2,"d":3},"y":2}

Xidel online tester.

Related