Search code examples
jsonnosqlxquery

Transform memory nodes to JSON


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?


Solution

  • --) 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.