0

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?

1 Answer 1

1

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>
Sign up to request clarification or add additional context in comments.

4 Comments

Note also, for future reference, that there is a proposal to allow the match pattern to be written as match="array(map(s:string, xs:string))".
Great, this works. Is there any doc that could point me to the correct syntax (or a tutorial on how to parse JSON)? I have to admit that I don't understand all those curly brackets ;-). In my real sample, I would have to use for-each-group in the next step to group over the field "text". How could this be done? But in the end, I think I will switch to a two-step process: first convert json to xml, then use a "classic" XSLT to process the xml. Reason: the "-json" command line argument is only supported in the Java parser HE, but not in the .NET version...
@wknauf, the .NET Saxon HE 10 release, like the Java HE 10 release, doesn't have the the -json command line option, as it was newly introduced in Saxon 11, I think. For .NET that means currently, SaxonCS from Saxonica and .NET 5 or 6, or, as I have managed to cross-compile Saxon HE 11.4 Java to .NET 6, or you could, if you have the .NET 6 SDK installed, try my recently release dotnet tool nuget.org/packages/SaxonHE11NetXslt/11.4.0-alpha1 which runs Saxon 11.4 HE within .NET 6 as a command line tool/app (with the -json option). As for the grouping, raise a new question on that.
The mapping of JSON to XDM is of course explained in the XPath 3.1 spec or in tutorials on XPath 3.1, like altova.com/training/xpath3.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.