JSON to XML Policy: Re-sequence XML nodes based on Schema

I am working on a proxy to convert Soap Service to Rest API that would allow JSON payload. It's using JSON to XML polity to convert JSON payload to XML. It works fine if incoming JSON elements are in sequence specified in wsdl but if JSON elements are not in order, call to soap service fails since it expects XML elements in specific order according to WSDL.

How can i reorder XML generated from JSON to XML policy according to the schema?

Appriciate any help.

Thanks.

1 12 2,507
12 REPLIES 12

You can apply an XSLT policy after JSONToXML to re-order XML elements.

For example, suppose you have this as the output of JSONToXML.

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
 <soap:Header>
  </soap:Header>
  <soap:Body>
    <OperationName>
      <ElementZ/>
      <ElementY/>
      <ElementX/>
    </OperationName>
  </soap:Body>
</soap:Envelope>

And you want a different order of the child elements of OperationName. Like this:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Header>
  </soap:Header>
  <soap:Body>
      <OperationName>
         <ElementX/>
         <ElementY/>
         <ElementZ/>
      </OperationName>
  </soap:Body>
</soap:Envelope>

To do that, you can apply this XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
                version="1.0">
  <xsl:output method="xml"
              indent="yes"/>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>


  <xsl:template match="OperationName">
    <xsl:copy>
      <xsl:apply-templates select="ElementX" />
      <xsl:apply-templates select="ElementY" />
      <xsl:apply-templates select="ElementZ" />
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

This approach will work regardless of the inbound ordering of the elements. If they are in the correct order, the XSLT will still work.

This DOES require that you hand-construct an XSLT for each schema in which the ordering of elements is important. I don't have a way to automatically generate the XSLT for every schema in which ordering is required. You might be able to build a generator that works that way, without too much trouble.

But I have to wonder whether "Fixing" the problem this way is a good thing. If the ordering really is significant, why not just tell clients to use the proper ordering in the JSON hash? Or, if the ordering really isn't significant, then change the WSDL (Schema) to relax that requirement, as described here.

The backend Soap service requires nodes in certain order. If xml payload doesn't have them in order, it fails.

The xml payload is really big with arrays and objects.

Is there a easier way to do it?

Former Community Member
Not applicable

The way I see it, you have two options:

1) Have the backend send the fields in the correct order.

2) Try JAXB. I believe JAXB will allow you to map the JSON to directly to SOAP even if the order is not preserved.

I am not a java developer so don't know how to use JAXB. we are trying to see if we can fix the issue in Apigee so we don't have to ask every proxy user to be careful about this. the sequence is specified in wsdl but not sure if that would help in anyway.

There's a general XSLT way to do it, see here.

The general XSLT needs access to the schema. I am unsure if you can configure the XSLT policy in Apigee Edge to access a schema file as a variable. You may have to play with it to see if you can get it to behave in the way you want. If that does not work, then you can run the XSL Transform within a Java callout, which for sure can access a schema file, for example a schema that you embed in the JAR as a resource.

Thanks Dino. I had the same question that how can i have the xsd from wsdl to a variable in Apigee. I guess I can try adding a resource file as xsd and see if I can use it. I am not a java developer so can't use java call out. is there a way to do the same in node/javascript?

I have an XSLT callout implemented in Java that you theoretically could use. Just looking at the code, the tests are not up to date. I am writing some tests now and will share with you the callout when I finish. This should be usable for you, with the "general XSLT" thing I linked to above, if you can isolate the XSD that should be used for each message.

EDIT: here's the callout. You don't need to be a Java developer to use it. You don't even need to compile it. The readme describes how to use it.

I don't know about implementing this in nodejs; it seems like it would perform better in Java.

@Dino,

I tried adding xsd as resource in proxy and tried reading it in xsl. doesn't seem to work. I tried following two options but don't think it's working.

how do I find out if I am doing something wrong or if this feature is not supported in Apigee?

document('xsd://Script-1.xsd')

document('Script-1.xsd')

Complete xsl:

<xsl:stylesheet version="2.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="#all"> 
  <xsl:output indent="yes"/> 
  <xsl:strip-space elements="*"/> 
  <xsl:variable name="input"> 
    <xsl:copy-of select="/"/> 
  </xsl:variable> 
  <xsl:template match="/*"> 
    <xsl:variable name="firstContext" select="name()"/> 
    <xsl:variable name="xsdElems" select="document('xsd://Script-1.xsd')/xs:schema/xs:element[@name=$firstContext]/xs:complexType/xs:sequence/xs:element/@name"/> 
    <xsl:element name="{$firstContext}"> 
      <xsl:for-each select="$xsdElems"> 
        <xsl:variable name="secondContext" select="."/> 
        <xsl:element name="{$secondContext}"> 
          <xsl:value-of select="$input/*/*[@name=$secondContext]/@value"/> 
        </xsl:element> 
      </xsl:for-each> 
    </xsl:element> 
  </xsl:template> 
</xsl:stylesheet>

Thanks,

Surabhi

You need to do something like this in your XSL:

<xsl:stylesheet version="2.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xsl:output indent="yes"
              method="xml"
              omit-xml-declaration="yes"
              />
  <xsl:strip-space elements="*"/>


  <xsl:param name="xsdstring" select="''"/>
  <xsl:variable name="xsd" select="document(concat('data:text/xml,',$xsdstring))"/>


  <xsl:variable name="input">
    <xsl:copy-of select="/"/>
  </xsl:variable>


  <xsl:template match="/*">
    <xsl:variable name="firstContext" select="name()"/>
    <xsl:variable name="xsdElems" select="$xsd/xs:schema/xs:element[@name=$firstContext]/xs:complexType/xs:sequence/xs:element/@name"/>
    <xsl:element name="{$firstContext}">
      <xsl:for-each select="$xsdElems">
        <xsl:variable name="secondContext" select="."/>
        <xsl:element name="{$secondContext}">
          <xsl:value-of select="$input/*/*[@name=$secondContext]/@value"/>
        </xsl:element>
      </xsl:for-each>
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

And the policy configuration should be like this :

<JavaCallout name='JavaCallout-Xslt-1'>
  <Properties>
     <Property name='xslt'>{xslturl}</Property>
     <Property name='engine'>saxon</Property>
     <Property name='input'>response</Property>
     <Property name='output'>response.content</Property>
     <!-- parameter to pass to the XSLT -->
     <Property name='param_xsdstring'>{variable-containing-xsd-string}</Property>
  </Properties>
  <ClassName>com.google.apigee.edgecallouts.xslt.XsltCallout</ClassName>
  <ResourceURL>java://edge-custom-xslt-1.0.5.jar</ResourceURL>
</JavaCallout>

Note the version of the jar: you need v1.0.5 of the callout to get this to work.

The XSD is passed as a string contained within a parameter. You need a variable containing that XSD, which you can populate with a ServiceCallout or a KVM-Get or whatever you like.

Another option is to specify a URL which returns the XSD. It looks like this:

<xsl:stylesheet version="2.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xsl:output indent="yes"
              method="xml"
              omit-xml-declaration="yes"
              />
  <xsl:strip-space elements="*"/>


  <xsl:param name="xsdurl" select="''"/>
  <xsl:variable name="xsd" select="document($xsdurl))"/>
   ...


And in that case the policy config is like this:

<JavaCallout name='JavaCallout-Xslt-1'>
  <Properties>
     <Property name='xslt'>{xslturl}</Property>
     <Property name='engine'>saxon</Property>
     <Property name='input'>response</Property>
     <Property name='output'>response.content</Property>
     <!-- parameter to pass to the XSLT -->
     <Property name='param_xsdurl'>https://my-server/schema.xsd</Property>
  </Properties>
  <ClassName>com.google.apigee.edgecallouts.xslt.XsltCallout</ClassName>
  <ResourceURL>java://edge-custom-xslt-1.0.5.jar</ResourceURL>
</JavaCallout>

See if that helps you.

Thanks @Dino. Let me try this out.

Former Community Member
Not applicable

You can use a JavaScript on the response flow to reorder the JSON before it is converted to XML. Or, you can use a XSLT (here is a sample on how that is done: https://stackoverflow.com/questions/8305258/rearrange-xml-nodes-including-sub-nodes-by-xslt)

Thank Dino.