Skip to content

Instantly share code, notes, and snippets.

@benibela
Forked from joewiz/json-xml.xqm
Last active Sep 29, 2020
Embed
What would you like to do?
An implementation of XQuery 3.1's fn:json-to-xml and fn:xml-to-json functions
xquery version "3.1";
(:~
: An implementation of XQuery 3.1"s fn:json-to-xml and fn:xml-to-json functions for eXist, which does not support them natively as of 4.3.0, and Xidel.
:
: @author Joe Wicentowski, Benito van der Zander
: @version 0.6
: @see http://www.w3.org/TR/xpath-functions-31/#json
:)
module namespace jx = "http://joewiz.org/ns/xquery/json-xml";
(:~
: Parses a string supplied in the form of a JSON text, returning the results in the form of an XML document node.
:
: @param $json-text A string supplied in the form of a JSON text
: @return The results in the form of an XML document node
: @see https://www.w3.org/TR/xpath-functions-31/#func-json-to-xml
:)
declare function jx:json-to-xml($json-text as xs:string?) as document-node()? {
jx:json-to-xml($json-text, map {})
};
(:~
: Parses a string supplied in the form of a JSON text, returning the results in the form of an XML document node.
:
: @param $json-text A string supplied in the form of a JSON text
: @param $options Used to control the way in which the parsing takes place
: @return The results in the form of an XML document node
: @see https://www.w3.org/TR/xpath-functions-31/#func-json-to-xml
:)
declare function jx:json-to-xml($json-text as xs:string?, $options as map(*)) as document-node()? {
$json-text ! document { jx:json-to-xml-recurse(parse-json(.,
if ($options?duplicates = "retain") then map:put($options, "duplicates", "use-first")
else $options
)) }
};
(:~
: Converts an XML tree, whose format corresponds to the XML representation of JSON defined in the XPath and XQuery 3.1 Functions & Operators specification, into a string conforming to the JSON grammar.
:
: @param $input An XML tree, whose format corresponds to the XML representation of JSON
: @return A string conforming to the JSON grammar
: @see https://www.w3.org/TR/xpath-functions-31/#func-xml-to-json
:)
declare function jx:xml-to-json($input as node()?) as xs:string? {
jx:xml-to-json($input, map {} )
};
(:~
: Converts an XML tree, whose format corresponds to the XML representation of JSON defined in the XPath and XQuery 3.1 Functions & Operators specification, into a string conforming to the JSON grammar.
:
: @param $input An XML tree, whose format corresponds to the XML representation of JSON
: @param $options Options for controlling the way in which the conversion takes place
: @return A string conforming to the JSON grammar
: @see https://www.w3.org/TR/xpath-functions-31/#func-xml-to-json
:)
declare function jx:xml-to-json($input as node()?, $options as map(*)) as xs:string? {
$input !
(try {
let $json := jx:xml-to-json-recurse(.)
let $serialization-parameters := map { "method": "json", "indent": $options?indent }
return serialize($json, $serialization-parameters)
} catch *:FORG0001 | *:SERE0020 | *:SERE0022 { jx:FOJS0006($input) }
)
};
(:~
: A utility function that recurses through a parsed JSON text, returning the results in the form of XML nodes.
:
: @param $json A parsed JSON text
: @return The results in the form of an XML document node
:)
declare %private function jx:json-to-xml-recurse($json as item()*) as item()+ {
let $data-type := jx:json-data-type($json)
return
element { QName("http://www.w3.org/2005/xpath-functions", $data-type) } {
if ($data-type eq "array") then
for $array-index in 1 to array:size($json)
let $array-member := $json($array-index)
let $array-member-data-type := jx:json-data-type($array-member)
return
element {$array-member-data-type} {
if ($array-member-data-type = ("array", "map")) then
jx:json-to-xml-recurse($array-member)/node()
else
$array-member
}
else if ($data-type eq "map") then
map:for-each(
$json,
function($object-name, $object-value) {
let $object-value-data-type := jx:json-data-type($object-value)
return
element { QName("http://www.w3.org/2005/xpath-functions", $object-value-data-type) } {
attribute key {$object-name},
if ($object-value-data-type = ("array", "map")) then
jx:json-to-xml-recurse($object-value)/node()
else
$object-value
}
}
)
else
$json
}
};
(:~
: A utility function for getting the data type of JSON data
:)
declare %private function jx:json-data-type($json as item()?) as xs:string{
if ($json instance of array(*)) then "array"
else if ($json instance of map(*)) then "map"
else if ($json instance of xs:string) then "string"
else if ($json instance of xs:double) then "number"
else if ($json instance of xs:boolean) then "boolean"
else if (empty($json)) then "null"
else error(xs:QName("ERR"), "Not a known data type for json data")
};
declare %private function jx:xml-to-json-recurse($input as node()*) as item()* {
for $node in $input
return typeswitch ($node)
case element(fn:map) return (
$node/text()[normalize-space(.) ne ""]/jx:FOJS0006($node),
map:merge(
$node/* ! (let $key := @key return
if ($key) then map {$key: jx:xml-to-json-recurse(.) }
else jx:FOJS0006($node))
)
)
case element(fn:array) return (
$node/text()[normalize-space(.) ne ""]/jx:FOJS0006($node),
array { $node/* } => array:for-each(jx:xml-to-json-recurse#1)
)
case element(fn:string) return
$node/string()
case element(fn:number) return
$node cast as xs:double
case element(fn:boolean) return
$node cast as xs:boolean
case element(fn:null) return
($node/text()[normalize-space(.) ne ""]/jx:FOJS0006($node))
case document-node() return
jx:xml-to-json-recurse($node/node())
(: Comments, processing instructions, and whitespace text node children of map and array are ignored :)
case text() return
if (normalize-space($node) eq "") then
()
else
jx:FOJS0006($node)
case comment() | processing-instruction() return
()
case element() return
jx:FOJS0006($node)
default return
error(xs:QName("ERR"), "Does not match known node types for xml-to-json data")
};
declare %private function jx:FOJS0006($node){
error(fn:QName("http://www.w3.org/2005/xqt-errors", "FOJS0006"), "Invalid XML representation of JSON: "||serialize($node))};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment