jsonxmlxsltxslt-3.0xml-to-json

XSLT to convert a XML to JSON which may have multiple repeated nodes


I want to convert a XML to JSON using XSLT. But I am facing few issues.

Input XML

<notifications xmlns="http://soap.sforce.com/2005/09/outbound">
  <OrganizationId>123</OrganizationId>
  <ActionId>123</ActionId>
  <SessionId xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
  <EnterpriseUrl>qwe</EnterpriseUrl>
  <PartnerUrl>qwe</PartnerUrl>
  <Notification>
    <Id>123</Id>
    <sObject xsi:type="sf:Opportunity" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sf="urn:sobject.enterprise.soap.sforce.com">
      <sf:Id>ao123</sf:Id>
      <sf:Amount>60000.0</sf:Amount>
      <sf:CreatedDate>2014-11-26T14:45:52.000Z</sf:CreatedDate>
      <sf:IsClosed>false</sf:IsClosed>
    </sObject>
  </Notification>
</notifications>

Expected Output JSON

{
  "notifications": {
    "OrganizationId": "123",
    "ActionId": "123",
    "SessionId": {
      "@nil": "true"
    },
    "EnterpriseUrl": "qwe",
    "PartnerUrl": "qwe",
    "Notification": [
      {
        "Id": "ao123",
        "sObject": {
          "@type": "sf:Opportunity",
          "Id": "ao123",
          "Amount": "60000.0",
          "CreatedDate": "2014-11-26T14:45:52.000Z",
          "IsClosed": "false"
        }
      }
    ]
  }
}

From this answer I got XSLT and I have tried it. This is the XSLT code I have tried. Fiddle link

<?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"
    xmlns="http://www.w3.org/2005/xpath-functions"
    expand-text="yes"
    version="3.0">

  <xsl:output method="text"/>

  <xsl:template match="/">
      <xsl:variable name="json-xml">
          <xsl:apply-templates/>
      </xsl:variable>
      <xsl:value-of select="xml-to-json($json-xml, map { 'indent' : true() })"/>
  </xsl:template>
  
  <xsl:template match="*[not(*)]">
    <string key="{local-name()}">{.}</string>
  </xsl:template>
  

  
  <xsl:template match="*[*]">
    <xsl:param name="key" as="xs:boolean" select="false()"/>
    <map>
      <xsl:if test="$key">
        <xsl:attribute name="key" select="local-name()"/>
      </xsl:if>
      <xsl:for-each-group select="*" group-by="node-name()">
          <xsl:choose>
              <xsl:when test="current-group()[2]">
                  <array key="{local-name()}">
                      <xsl:apply-templates select="current-group()">
                        <xsl:with-param name="key" select="false()"/>
                      </xsl:apply-templates>
                  </array>
              </xsl:when>
              <xsl:otherwise>
                  <xsl:apply-templates select="current-group()">
                    <xsl:with-param name="key" select="true()"/>
                  </xsl:apply-templates>
              </xsl:otherwise>
          </xsl:choose>
      </xsl:for-each-group>
    </map>
  </xsl:template>

</xsl:stylesheet>

So below are few issues which I am facing

Please note that I don't want to hard code node names other than notifications and Notification in XSLT code as I may receive different nodes under node Notification.

I am looking for XSLT which can handle my requirements


Solution

  • Some of the requirements (outer map/JSON object, always making Notification an array) can of course be inserted into the XSLT code by modifying what you linked to (please make sure next time you show the XSLT in the question as well):

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        exclude-result-prefixes="#all"
        xmlns="http://www.w3.org/2005/xpath-functions"
        expand-text="yes"
        version="3.0">
    
      <xsl:output method="text"/>
    
      <xsl:template match="/">
          <xsl:variable name="json-xml">
            <map>
              <xsl:apply-templates/>
            </map>
          </xsl:variable>
          <xsl:value-of select="xml-to-json($json-xml, map { 'indent' : true() })"/>
      </xsl:template>
      
      <xsl:template match="*[not(*)]">
        <string key="{local-name()}">{.}</string>
      </xsl:template>
      
      <xsl:template match="*[*]">
        <xsl:param name="key" as="xs:boolean" select="true()"/>
        <map>
          <xsl:if test="$key">
            <xsl:attribute name="key" select="local-name()"/>
          </xsl:if>
          <xsl:for-each-group select="*" group-by="node-name()">
              <xsl:choose>
                  <xsl:when test="current-group()[2] or current-grouping-key() = QName('http://soap.sforce.com/2005/09/outbound', 'Notification')">
                      <array key="{local-name()}">
                          <xsl:apply-templates select="current-group()">
                            <xsl:with-param name="key" select="false()"/>
                          </xsl:apply-templates>
                      </array>
                  </xsl:when>
                  <xsl:otherwise>
                      <xsl:apply-templates select="current-group()">
                        <xsl:with-param name="key" select="true()"/>
                      </xsl:apply-templates>
                  </xsl:otherwise>
              </xsl:choose>
          </xsl:for-each-group>
        </map>
      </xsl:template>
    
    </xsl:stylesheet>
    

    The code you grabbed from another question/answer I think was not written with XML having elements with attributes in mind; I have tried to adapt it below with

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        exclude-result-prefixes="#all"
        xmlns="http://www.w3.org/2005/xpath-functions"
        expand-text="yes"
        version="3.0">
    
      <xsl:output method="text"/>
    
      <xsl:template match="/">
          <xsl:variable name="json-xml">
            <map>
              <xsl:apply-templates/>
            </map>
          </xsl:variable>
          <xsl:value-of select="xml-to-json($json-xml, map { 'indent' : true() })"/>
      </xsl:template>
      
      <xsl:template match="*[not(*)]">
        <string key="{local-name()}">{.}</string>
      </xsl:template>
      
      <xsl:template match="@*">
        <string key="@{local-name()}">{.}</string>
      </xsl:template>
      
      <xsl:template match="*[* or @*]">
        <xsl:param name="key" as="xs:boolean" select="true()"/>
        <map>
          <xsl:if test="$key">
            <xsl:attribute name="key" select="local-name()"/>
          </xsl:if>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="*" group-by="node-name()">
              <xsl:choose>
                  <xsl:when test="current-group()[2] or current-grouping-key() = QName('http://soap.sforce.com/2005/09/outbound', 'Notification')">
                      <array key="{local-name()}">
                          <xsl:apply-templates select="current-group()">
                            <xsl:with-param name="key" select="false()"/>
                          </xsl:apply-templates>
                      </array>
                  </xsl:when>
                  <xsl:otherwise>
                      <xsl:apply-templates select="current-group()">
                        <xsl:with-param name="key" select="true()"/>
                      </xsl:apply-templates>
                  </xsl:otherwise>
              </xsl:choose>
          </xsl:for-each-group>
        </map>
      </xsl:template>
    
    </xsl:stylesheet>
    

    That seems to give the wanted attributes as @name properties of a JSON object/map for your sample; I can't guarantee it will work in general.