Transform memory nodes to JSON

Viewed 486

I need to transform der memory node to JSON. Expected output:

{
    "citations": [
        {
            "cited": "classes",
            "procceding": [
                "applied",
                "considered",
                "followed"
            ]
        },
        {
            "cited": "toCase",
            "procceding": [
                "Ty Corp Ltd v Nu Inc",
                "PY Arbitrage v Bank of WN"
            ]
        }
    ]
}

My XQuery:

let $nodes :=
<cases>
  <citations>
    <classes>
      <text>applied</text>
      <text>considered</text>
      <text>followed</text>
    </classes>
    <toCase>
      <text>Ty Corp Ltd v Nu Inc</text>
      <text>PY Arbitrage v Bank of WN</text>
    </toCase>
  </citations>
</cases>
return     
let $map := map:map()
let $p :=
    for $node in $nodes/citations/node()
    let $nodeName := local-name($node)
    let $c :=
      map:put($map, "citations", map:new(map:entry('cited', map:new(map:entry($nodeName, (for $value in $node/node() return $value/fn:string(.)) ))) )  )
    return $c
return xdmp:to-json($map)

$nodes is wrongly transformed:

{"citations": {"cited": {"toCase": ["Ty Corp Ltd v Nu Inc", "PY Arbitrage v Bank of WN"]}}

How can I get the expected JSON?

2 Answers

--) You can use json:object() to scaffold in-memory JSON model ( equivalent of XML schema ) and preserve the Object order. I refactor and streamline one of my xml-to-json modules as below. It should fulfill your needs.

declare function local:marshal-json(
  $master-node as node()
) as item()
{
  let $master-object := json:object()
  let $_ :=
    for $child-node in $master-node/node()
    let $child-name := local-name($child-node)
    return
      map:put($master-object, $child-name,
          for $floor in $child-node/node() return local:floor-object($floor)
      )      
  return $master-object        
};

declare function local:floor-object(
  $child-node as node()  
) as item()
{
  let $floor-object := json:object()
  let $_ := map:put($floor-object, "cited", local-name($child-node))
  let $floor-values := json:array()
  let $_ :=
    for $gc in $child-node/node()
    return json:array-push($floor-values, $gc/fn:string(.))
  let $_ := map:put($floor-object, "procceding", $floor-values)  
  return
    $floor-object  
};

let $nodes :=
<cases>
  <citations>
    <classes>
      <text>applied</text>
      <text>considered</text>
      <text>followed</text>
    </classes>
    <toCase>
      <text>Ty Corp Ltd v Nu Inc</text>
      <text>PY Arbitrage v Bank of WN</text>
    </toCase>
  </citations>
</cases>
return 
  local:marshal-json($nodes)

--) I am not in a position to disapprove over your design. But I approve the spirit. You should be able to transform the XML to JSON (with parameters not hardcoded JSON names) using similar logic.

Example: Transform

<investment>
    <entity>
        <name>XYZ</name>
    </entity>
    <bogus>encrypted</bogus>
    <property>
        <Canadian>
            <propertyType>eligible dividend of CPC</propertyType>
            <notionalAmount>10000</notionalAmount>
        </Canadian>
        <foreign>
            <propertyType>dividend of trust</propertyType>
            <notionalAmount>7000</notionalAmount>
            <foreignWithholding>660</foreignWithholding>
        </foreign>
    </property>
    <capital>
        <portfolio>
            <name>HF25</name>
            <inadequateConsiderationFMV>10000</inadequateConsiderationFMV>
            <transferredAmount>15000</transferredAmount>
            <gainLossOnDisposition>11000</gainLossOnDisposition>
        </portfolio>
        <portfolio>
            <name>UL1</name>
            <superficialLoss>17000</superficialLoss>
            <reacquired>5000</reacquired>
            <gainLossOnDisposition>-2000</gainLossOnDisposition>
        </portfolio>
        <security-CUSIP1>
            <gainLossOnDisposition>-3600</gainLossOnDisposition>
        </security-CUSIP1>
    </capital>
</investment>

to JSON:

{
    "entity": {
        "name": "XYZ"
    },
    "bogus": null,
    "property": {
        "Canadian": {
            "propertyType": "eligible dividend of CPC",
            "notionalAmount": 10000
       },
        "foreign": {
            "propertyType": "dividend of trust",
            "notionalAmount": 7000,
            "foreignWithholding": 660
        }
    },
    "capital": {
        "portfolio": [
            {
                "name": "HF25",
                "inadequateConsiderationFMV": 10000,
                "transferredAmount": 15000,
                "gainLossOnDisposition": 11000
            },
            {
                "name": "UL1",
                "superficialLoss": 17000,
                "reacquired": 5000,
                "gainLossOnDisposition": -2000
            }
        ], 
        "security-CUSIP1": {
            "gainLossOnDisposition": -3600
        }
    }
}

I use low-level APIs with more leverage: It transforms XML string/text to JSON string or number upon instructions; It conforms to the customised JSON model, object and array structure; It observes XML/JSON naming convention in financial domain.

--) That said, this programming model is memory-intensive. If XSLT3 parses and transforms JSON in streaming model in Java, it could be more memory-efficient. I never use XSLT3 JSON parser as now and can’t comment on its efficacy.

let $nodes :=
  <cases>
    <citations>
      <classes>
        <text>applied</text>
        <text>considered</text>
        <text>followed</text>
      </classes>
      <toCase>
        <text>Ty Corp Ltd v Nu Inc</text>
        <text>PY Arbitrage v Bank of WN</text>
      </toCase>
    </citations>
  </cases>
return     

let $citations := 
  for $node in $nodes/citations/node()
  let $nodeName := local-name($node)
  return 
    map:new(( 
      map:entry('cited', $nodeName), 
      map:entry("procceding", for $value in $node/node() return $value/fn:string(.) )
    ))   
return 
  map:entry("citations", $citations) => xdmp:to-json() 
Related