Skip to content

Instantly share code, notes, and snippets.

@paxtonhare
Created August 11, 2016 20:25
Show Gist options
  • Save paxtonhare/0b61e46ca99a38ebcbef4551a1ba2838 to your computer and use it in GitHub Desktop.
Save paxtonhare/0b61e46ca99a38ebcbef4551a1ba2838 to your computer and use it in GitHub Desktop.
<export><workspace name="json-tree-walker"><query name="INSERT-DOCS" focus="false" listorder="1" taborder="1" active="true" database="12188229314415601684" server="3177585694450821129" database-name="Documents" server-name="App-Services" mode="xquery">(: Insert our Test Documents :)
(: a document with an object node at the root :)
xdmp:document-insert('/test.json', xdmp:unquote('{
"string": "a string value",
"boolean-false": false,
"boolean-true": true,
"number-negative": -123,
"number-positive": 123,
"null": null,
"array": [
"an",
"array",
"of",
"strings"
],
"array-of-objects": [
{
"an-object": "within-an-array"
},
{
"a-positive-number": 456
}
],
"object": {
"another": "object"
}
}')),
(: a document with an array node at the root :)
xdmp:document-insert('/test2.json', xdmp:unquote('[
{
"first": "object"
},
{
"string": "a string value",
"boolean-false": false,
"boolean-true": true,
"number-negative": -123,
"number-positive": 123,
"null": null,
"array": [
"an",
"array",
"of",
"strings"
],
"array-of-objects": [
{
"an-object": "within-an-array"
},
{
"a-positive-number": 456
}
],
"array-of-numbers": [1, 2, 3],
"array-of-arrays": [
[
"sub-array1",
{
"obj": 123
},
[
"sub-sub-array1"
],
[
"sub-sub-array2"
]
],
[
"sub-array2"
],
[
"sub-array3"
]
],
"object": {
"another": "object"
}
},
{
"last": "object"
}
]')),
(: a document with a number node at the root :)
xdmp:document-insert("/test3.json", number-node{ 5 }),
(: a document with a boolean node at the root :)
xdmp:document-insert("/test4.json", boolean-node{ fn:true() }),
(: a document with a null node at the root :)
xdmp:document-insert("/test5.json", null-node{ }),
(: a document with a text node at the root :)
xdmp:document-insert("/test6.json", text{ "a string" }),
(: an xml document just for grins :)
xdmp:document-insert("/test7.json", &lt;this-is-a-dirty-trick/&gt;)</query><query name="WALK-TEST" focus="true" listorder="2" taborder="2" active="true" database="12188229314415601684" server="3177585694450821129" database-name="Documents" server-name="App-Services" mode="xquery">xquery version "1.0-ml";
declare namespace walker = "http://marklogic.com/ns/json-tree-walker";
declare option xdmp:mapping "false";
declare %private function walker:_walk-json($nodes as node()*, $o as json:object?, $visitor-func as function(*)) {
(:
: This closure handles some of the boilerplate of calling the visitor
: It merely exists to keep the rest of this function cleaner
:)
let $call-visitor := function($key, $value) {
let $response-map := map:new((
map:entry("key", $key),
map:entry("value", $value)
))
let $_ := $visitor-func($key, $value, $response-map)
return $response-map
}
for $n in $nodes
(: if this node has a name then turn it into a string. This is the json key.
: We use the ! operator to conditionally assign the string. If no node name exists
: then $key will be the empty sequence
: See https://developer.marklogic.com/blog/simple-mapping-operator for more details on !
:)
let $key := fn:node-name($n) ! fn:string(.)
return
typeswitch($n)
case document-node() return
(: if it's a document node then start with the root node :)
walker:_walk-json($n/node(), $o, $visitor-func)
case object-node() return
(: create an in-memory json object :)
let $oo := json:object()
(: recursively walk every child of this object and put
them into our json object :)
let $_ := walker:_walk-json($n/node(), $oo, $visitor-func)
(: give our visitor function a chance to alter the key or value :)
let $r := $call-visitor($key, $oo)
let $key := map:get($r, "key")
let $value := map:get($r, "value")
return
(: any non-root object will have a name :)
if ($key and fn:exists($o) and fn:exists($value)) then
( map:put($o, $key, $value), $o )
(: return the new object :)
else
$value
case array-node() return
(: create an in-memory json array to hold the values :)
let $aa := json:to-array(walker:_walk-json($n/node(), (), $visitor-func))
(: give our visitor function a chance to alter the key or value :)
let $r := $call-visitor($key, $aa)
let $key := map:get($r, "key")
let $value := map:get($r, "value")
return
if (fn:exists($o) and fn:exists($value)) then
( map:put($o, $key, $value), $o )
else
$value
case number-node() |
boolean-node() |
null-node() |
text() return
(: give our visitor function a chance to alter the key or value :)
let $r := $call-visitor($key, $n)
let $key := map:get($r, "key")
let $value := map:get($r, "value")
return
if (fn:exists($o) and fn:exists($value)) then
( map:put($o, $key, $value), $o )
else
$value
(: this is our failsafe in case we missed something :)
default return
$n
};
(:~
: The public entry point for this library
:
: @param $nodes - the nodes to walk
: @param $visitor-func - a closure to call when a node is visited
:
: @return - the transformed json
:)
declare function walker:walk-json($nodes as node()*, $visitor-func as function(*))
{
walker:_walk-json($nodes, (), $visitor-func)
};
for $doc in fn:doc()
return
walker:walk-json($doc, function($key, $value, $output) {
if ($value instance of json:object) then
(: upcase all object keys :)
map:put($output, "key", fn:upper-case($key))
else if ($value instance of json:array) then
(: reverse every array :)
map:put($output, "value", json:to-array(fn:reverse(json:array-values($value))))
else if ($value instance of number-node()) then
(: negate any number :)
map:put($output, "value", -$value)
else if ($value instance of boolean-node()) then
(: invert any boolean :)
map:put($output, "value", fn:not(xs:boolean($value)))
else if ($value instance of null-node()) then
(: omit nulls by returning the empty sequence for the value :)
map:put($output, "value", ())
else
map:put($output, "value", $value)
})</query></workspace></export>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment