Site
Site

Reputation: 145

Use XSLT to extract information from JSON and output format as JSON

With some help, I have put in place the following script which takes a JSON file as a parameter and outputs it as an XML.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">
    <xsl:param name="json" as="xs:string">
    [
       {
          "ID":"DWL",
          "profiles":[
             {
                "firstName":"Contact",
                "lastName":"Sample",
                "emailAddresses":[
                   {
                      "emailAddress":"[email protected]"
                   }
                ]
             }
          ]
       },
       {
          "ID":"DWLK",
          "profiles":[
             {
                "firstName":"Contact",
                "lastName":"Sample",
                "emailAddresses":[
                   {
                      "emailAddress":"[email protected]",
                      "primary":true
                   }
                ]
             }
          ]
       }
    ] 
  </xsl:param>
    <xsl:template match="/" name="xsl:initial-template">
        <xsl:sequence select="json-to-xml($json)"/>
    </xsl:template>
</xsl:stylesheet>

I need to modify the script in such a way to extract the ID, profiles.FirstName, and profiles.emailAddresses and also add an extra static field origin. The final output should be the following:

[  
   {  
      "ID":"DWL",
      "origin":"static",
      "profiles":[  
         {  
            "firstName":"Contact",
            "emailAddresses":[  
               {  
                  "emailAddress":"[email protected]"
               }
            ]
         }
      ]
   },
   {  
      "ID":"DWLK",
      "origin":"static",
      "profiles":[  
         {  
            "firstName":"Contact",
            "emailAddresses":[  
               {  
                  "emailAddress":"[email protected]",
                  "primary":true
               }
            ]
         }
      ]
   }
]

Upvotes: 0

Views: 3199

Answers (2)

Michael Kay
Michael Kay

Reputation: 163418

There are basically two ways of manipulating JSON using XSLT 3.0: you can handle it as maps-and-arrays, or you can convert it to XML (and then back again). In a paper at XML Prague 2016 (available at http://www.saxonica.com/papers/xmlprague-2016mhk.pdf) I explored a couple of use cases comparing the two techniques, and I generally found conversion to XML and back easier, the main reason being that the pattern matching mechanisms for XML structures are much more flexible than pattern matching for maps and arrays (which in turn is because XML trees allow you to navigate upwards to examine your context, while maps and arrays don't).

Using that approach, you would convert to XML, then do a standard conversion on the XML something like this:

<xsl:mode name="add-origin" on-no-match="shallow-copy"/>

<xsl:template match="fn:map[*[@key='ID']]" mode="add-origin">
  <xsl:copy>
    <xsl:copy-of select="@*"/>  
    <fn:string key="origin">static</fn:string>
    <xsl:copy-of select="node()"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="/" name="xsl:initial-template">
  <xsl:variable name="converted">  
    <xsl:apply-templates select="json-to-xml($json)" mode="add-origin"/>
  </xsl:variable>
  <xsl:sequence select="xml-to-json($converted)"/>
</xsl:template>

You mention "extracting" some of the information from the input; I'm not sure I understand that requirement, but extracting information from JSON can be done easily enough from the maps-and-arrays representation delivered by parse-json(), using the lookup operator "?". For example you could get all the ID values using $json?*?ID, or you could get the first name of the person with ID JWL using $json?*[?ID='JWL']?profiles?1?firstName.

Upvotes: 4

Martin Honnen
Martin Honnen

Reputation: 167641

The first conversion with <xsl:sequence select="json-to-xml($json)"/> is shown at https://xsltfiddle.liberty-development.net/bnnZWD/5 and gives the XML

<array xmlns="http://www.w3.org/2005/xpath-functions">
   <map>
      <string key="ID">DWL</string>
      <array key="profiles">
         <map>
            <string key="firstName">Contact</string>
            <string key="lastName">Sample</string>
            <array key="emailAddresses">
               <map>
                  <string key="emailAddress">[email protected]</string>
               </map>
            </array>
         </map>
      </array>
   </map>
   <map>
      <string key="ID">DWLK</string>
      <array key="profiles">
         <map>
            <string key="firstName">Contact</string>
            <string key="lastName">Sample</string>
            <array key="emailAddresses">
               <map>
                  <string key="emailAddress">[email protected]</string>
                  <boolean key="primary">true</boolean>
               </map>
            </array>
         </map>
      </array>
   </map>
</array>

if you use that as an intermediary result and push it through some templates (https://xsltfiddle.liberty-development.net/bnnZWD/6)

  <xsl:template match="/" name="xsl:initial-template">
      <xsl:variable name="json-xml" select="json-to-xml($json)"/>
      <xsl:apply-templates select="$json-xml/node()"/>
  </xsl:template>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:template match="string[@key = 'ID']">
      <xsl:next-match/>
      <string key="origin">static</string>
  </xsl:template>

  <xsl:template match="string[@key = 'lastName']"/>

for the change you want you get:

<array xmlns="http://www.w3.org/2005/xpath-functions">
   <map>
      <string key="ID">DWL</string>
      <string key="origin">static</string>
      <array key="profiles">
         <map>
            <string key="firstName">Contact</string>
            <array key="emailAddresses">
               <map>
                  <string key="emailAddress">[email protected]</string>
               </map>
            </array>
         </map>
      </array>
   </map>
   <map>
      <string key="ID">DWLK</string>
      <string key="origin">static</string>
      <array key="profiles">
         <map>
            <string key="firstName">Contact</string>
            <array key="emailAddresses">
               <map>
                  <string key="emailAddress">[email protected]</string>
                  <boolean key="primary">true</boolean>
               </map>
            </array>
         </map>
      </array>
   </map>
</array>

You can then convert the transformed XML back to JSON using xml-to-json : (https://xsltfiddle.liberty-development.net/bnnZWD/7)

  <xsl:output method="text"/>

  <xsl:template match="/" name="xsl:initial-template">
      <xsl:variable name="json-xml" select="json-to-xml($json)"/>
      <xsl:variable name="transformed-json-xml">
            <xsl:apply-templates select="$json-xml/node()"/>
      </xsl:variable>
      <xsl:value-of select="xml-to-json($transformed-json-xml, map { 'indent' : true() })"/>
  </xsl:template>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:template match="string[@key = 'ID']">
      <xsl:next-match/>
      <string key="origin">static</string>
  </xsl:template>

  <xsl:template match="string[@key = 'lastName']"/>

and get with Saxon 9.8 the output

  [ 
    { "ID" : "DWL",
      "origin" : "static",
      "profiles" : 
      [ 
        { "firstName" : "Contact",
          "emailAddresses" : 
          [ 
            { "emailAddress" : "[email protected]" } ] } ] },

    { "ID" : "DWLK",
      "origin" : "static",
      "profiles" : 
      [ 
        { "firstName" : "Contact",
          "emailAddresses" : 
          [ 
            { "emailAddress" : "[email protected]",
              "primary" : true } ] } ] } ]

Cleaning up intermediary steps the code can be shortened to

  <xsl:output method="text"/>

  <xsl:template match="/" name="xsl:initial-template">
      <xsl:variable name="transformed-json-xml">
            <xsl:apply-templates select="json-to-xml($json)/node()"/>
      </xsl:variable>
      <xsl:value-of select="xml-to-json($transformed-json-xml, map { 'indent' : true() })"/>
  </xsl:template>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:template match="string[@key = 'ID']">
      <xsl:next-match/>
      <string key="origin">static</string>
  </xsl:template>

  <xsl:template match="string[@key = 'lastName']"/>

https://xsltfiddle.liberty-development.net/bnnZWD/9

And of course instead of using a parameter with the JSON string contents you could use unparsed-text to load from a JSON file e.g. <xsl:param name="json" select="unparsed-text('file.json')"/>.

Upvotes: 2

Related Questions