Search code examples
xsltsaxon

How to handle JSON input with Saxon XSLT


Recent Saxon releases contain a command line argument "-json:myfile.json" to input a JSON file.

But how do I implement the XSLT to parse this JSON? I did not find any doc which handles this directly (without making use of "json-to-xml" or similar).

I only found this: how to convert json to xml with saxonjs?

But this does not help me, because my json starts with an array:

[
  {
    "eid": "2122.5",
    "ecat": "show",
    "day": "1629410400",
    "spcat": "Bühne",
    "time": "19:30",
    "text": "Welle",
    "remarks": "",
    "location": ""
  },
  {
    "eid": "2122.6",
    "ecat": "show",
    "day": "1629496800",
    "spcat": "Bühne",
    "time": "19:30",
    "text": "Welle",
    "remarks": "",
    "location": ""
  }
]

By writing an intermediate XSLT using the function "json-to-xml", I can convert this JSON to xml that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<array xmlns="http://www.w3.org/2005/xpath-functions">
   <map>
      <string key="eid">2122.5</string>
      <string key="ecat">show</string>
      <string key="day">1629410400</string>
      <string key="spcat">Bühne</string>
      <string key="time">19:30</string>
      <string key="text">Welle</string>
      <string key="remarks"/>
      <string key="location"/>
   </map>
   <map>
      <string key="eid">2122.6</string>
      <string key="ecat">show</string>
      <string key="day">1629496800</string>
      <string key="spcat">Bühne</string>
      <string key="time">19:30</string>
      <string key="text">Welle</string>
      <string key="remarks"/>
      <string key="location"/>
   </map>
</array>

How can I create a template that matches the root item, and how can I call "apply-templates" to trigger another template that handles the items?


Solution

  • The JSON you have shown is an array so it will be mapped to the XPath/XSLT XDM type array(*), or, in your case, array(map(xs:string, xs:string)). To match on that use e.g. <xsl:template match=".[. instance of array(*)]">..</xsl:template> or e.g. <xsl:template match=".[. instance of array(map(xs:string, xs:string))]">..</xsl:template>.

    More complete example would be online at the fiddle, doing:

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="3.0"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns:map="http://www.w3.org/2005/xpath-functions/map"
      exclude-result-prefixes="#all"
      expand-text="yes">
      
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:template match=".[. instance of array(map(xs:string, xs:string))]">
        <items>
          <xsl:apply-templates select="?*"/>
        </items>
      </xsl:template>
      
      <xsl:template match=".[. instance of map(xs:string, xs:string)]">
        <xsl:variable name="map" select="."/>
        <item>
          <xsl:iterate select="map:keys(.)">
            <value key="{.}">{$map(.)}</value>
          </xsl:iterate>      
        </item>
      </xsl:template>
      
    </xsl:stylesheet>
    

    As for grouping:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="3.0"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns:map="http://www.w3.org/2005/xpath-functions/map"
      exclude-result-prefixes="#all"
      expand-text="yes">
      
      <xsl:output method="xml" indent="yes"/>
    
      <xsl:template match=".[. instance of array(map(xs:string, xs:string))]">
        <items>
          <xsl:for-each-group select="?*" group-by="?text">
            <group name="{current-grouping-key()}">
              <xsl:apply-templates select="current-group()"/>
            </group>
          </xsl:for-each-group>
        </items>
      </xsl:template>
      
      <xsl:template match=".[. instance of map(xs:string, xs:string)]">
        <xsl:variable name="map" select="."/>
        <item>
          <xsl:iterate select="map:keys(.)[not(. = current-grouping-key())]">
            <value key="{.}">{$map(.)}</value>
          </xsl:iterate>      
        </item>
      </xsl:template>
      
    </xsl:stylesheet>