0

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

  • notifications node is missing in json output, it is the root node in the xml.
  • Notification should be a json array even if I receive one item in XML

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

techresearch
  • 119
  • 3
  • 14
  • Your xml Notification node is not valid for array, you have to learn xml rules at first – Serge Jan 02 '23 at 15:59
  • @Serge What i am saying there is chance of getting multiple Notification node in xml, In this sample xml input there are two Notification and i am getting json as array which is expected https://xsltfiddle.liberty-development.net/bFksq1w/2 .So when i have one Notification node also i should get json array.That means Notification node should be array. Json output format should be uniform irrespective of number of Notification xml node – techresearch Jan 02 '23 at 16:10
  • My point is you have to learn how to create XML array , before trying to deserialize it to array. Now your syntax shows that it is an object. How do you think a compiler should know that you want it to be an array instead? – Serge Jan 02 '23 at 16:13
  • I am able to handle array issue with using proper conditions in cases of for-each-group. Here is the xslt https://xsltfiddle.liberty-development.net/bFksq1w/4 . But still root node notifications is missing in JSON. – techresearch Jan 02 '23 at 16:46

1 Answers1

2

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.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Nice. This works as expected. As you suggested I have added the XSLT in the question as well now. Only small issue now is in the expected JSON also have the attrib from xml "@nil": "true" under SessionId and also "@type": "sf:Opportunity" under SObject. These are not present in XSLT output now. How i can manage these attributes – techresearch Jan 02 '23 at 17:31
  • @techresearch, I have added a second, adapted XSLT sample that tries to process attributes as well. Seems to do the job for your sample, it is always difficult to tell if you adapt some previous code meant for simpler input to more complex requirements if it doesn't break something or if it works in general, so test carefully on your own. – Martin Honnen Jan 02 '23 at 17:49
  • Thanks!! , This works as expected. As you mentioned, I will make sure to test it thoroughly. – techresearch Jan 02 '23 at 17:59