Wednesday, January 3, 2018

XSLT for XML to JSON transformation in WSO2 ESB

Introduction

You might be running in problems when ever you want to transform a xml payload to a respective json payload by having the freedom to manipulate the json element types. ex: you might want to keep a json element as a json array even it's only consists with a single json object. Another instance would be when you want to have the type as a String for an ID which contains only numbers, then what ESB would do is convert it right way to a json number. 

In those similar instances you might want to do some customization to have it in your way. one method is to develop a XSLT to do the transformation. 

Please see the following XSLT which I have been using for the requirement and I will explain what each part of the transformation does.

<xsl:stylesheet
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:json="http://json.org"
 version="1.0">

 <xsl:output method="text" indent="yes" encoding="UTF-8"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="skip-root" select="false()"/>

 <xsl:template match="/">
  <xsl:text>{</xsl:text>
  <xsl:choose>
   <xsl:when test="$skip-root = 'true'">
    <xsl:apply-templates select="child::node()/child::node()"/>
   </xsl:when>
   <xsl:otherwise>
    <xsl:apply-templates select="child::node()"/>
   </xsl:otherwise>
  </xsl:choose>
  <xsl:text>}</xsl:text>
 </xsl:template>

 <xsl:template match="@* | *">
  <xsl:if test="preceding-sibling::*">
   <xsl:text>,</xsl:text>
  </xsl:if>
  <xsl:choose>
   <xsl:when test="parent::*/@json:type = 'array'"/>
   <xsl:otherwise>
    <xsl:text>"</xsl:text>
    <xsl:value-of select="local-name()"/>
    <xsl:text>"</xsl:text>
    <xsl:text>:</xsl:text>
   </xsl:otherwise>
  </xsl:choose>
  <xsl:choose>
   <xsl:when test="@json:type = 'array'">
    <xsl:text>[</xsl:text>
    <xsl:apply-templates select="child::*"/>
    <xsl:text>]</xsl:text>
   </xsl:when>
   <xsl:when test="@json:type = 'object' or count(attribute::*[not(namespace-uri() = 'http://json.org') and not(namespace-uri() = 'http://www.w3.org/2001/XMLSchema-instance')] | child::*) > 0">
    <xsl:text>{</xsl:text>
    <xsl:for-each select="attribute::*[not(namespace-uri() = 'http://json.org') and not(namespace-uri() = 'http://www.w3.org/2001/XMLSchema-instance')] | child::*">
     <xsl:if test="not(preceding-sibling::*) and position() != 1">
      <xsl:text>,</xsl:text>
     </xsl:if>
     <xsl:apply-templates select="current()"/>
    </xsl:for-each>
    <xsl:text>}</xsl:text>
   </xsl:when>
   <xsl:otherwise>
    <xsl:call-template name="value"/>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>

    <xsl:template name="value">
     <xsl:variable name="value">
  <xsl:value-of select="text() | ."/>
 </xsl:variable>
     <xsl:choose>
      <xsl:when test="not(normalize-space($value))">
   <xsl:when test="@xsi:nil = 'true'">
    <xsl:text>null</xsl:text>
   </xsl:when>
   <xsl:otherwise>
    <xsl:text>""</xsl:text>
   </xsl:otherwise>
      </xsl:when>
      <xsl:when test="@json:type = 'string'">
       <xsl:text>"</xsl:text>
   <xsl:value-of select="$value"/>
       <xsl:text>"</xsl:text>
      </xsl:when>
      <xsl:when test="string(number($value)) != 'NaN' ">
       <xsl:value-of select="$value"/>
      </xsl:when>
  <xsl:when test="translate($value, 'TRUE', 'true') = 'true'">
   <xsl:text>true</xsl:text>
  </xsl:when>
  <xsl:when test="translate($value, 'FALSE', 'false') = 'false'">
   <xsl:text>false</xsl:text>
  </xsl:when>
  <xsl:otherwise>
       <xsl:text>"</xsl:text>
   <xsl:value-of select="$value"/>
       <xsl:text>"</xsl:text>
  </xsl:otherwise>
     </xsl:choose>
    </xsl:template>
</xsl:stylesheet>


You can specify with the param "skip-root" whether you want to skip that the wrapping element. This is basically needed when you are given a payload wrapped with a <jsonObject>.

Here the namespace json has been used to identify json data types for each attribute should be represented. and the xsi namespace has been used to force to have null values when the "nil=true" value is there as an attribute. Otherwise the default value is for the empty element would be an "" (empty) string.

Rest was designed to automatically handle the attributes and child elements the same and populate the json payload as necessary.

To use this xslt you might want to use the following with your sequence after the transformation.

<payloadFactory media-type="json">
<format>$1</format>
<args><arg evaluator="xml" expression="$body/*[local-name()='text']"/></args>
</payloadFactory>

Please remember to set the messageType property to "application/json" too.