XSLT is easy, even for transforming JSON!

Most developers I talk to will cringe if they hear the acronym XSLT.  I suspect that reaction is derived from some past experience where they have seen some horrendously complex XML/XSLT combination.  There is certainly lots of that around. However, for certain types of document transformations, XSLT can be a very handy tool and with the right approach, and as long as you avoid edge cases, it can be fairly easy.

When I start building an XSLT transform, I always start with the “identity” transform,

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes" />
<xsl:template match="/ | @* | node()"> <xsl:copy> <xsl:apply-templates select="* | @* | node()" /> </xsl:copy> </xsl:template>
</xsl:stylesheet>

OptimusPrimeThe identity transform simply traverses the nodes in the input document and copies them into the output document.

Make some changes

In order to make changes to the output document you need to add templates that will do something other than simply copy the existing node.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
   
<xsl:output method="xml" indent="yes" />
<xsl:template match="/ | @* | node()"> <xsl:copy> <xsl:apply-templates select="* | @* | node()" /> </xsl:copy> </xsl:template>
<!-- Change something --> <xsl:template match="foo"> <xsl:element name="bar"> <xsl:apply-templates select="* | @* | node()" /> </xsl:element> </xsl:template>
</xsl:stylesheet>

This template matches on any element named foo and in it’s place creates an element named bar that contains a copy of everything that foo contained.  XSLT will always use the template that matches most specifically.

Given the above XSLT, an input XML document like this,

<baz>  
    <foo value=”10text=”Hello World/>
</baz>

would be transformed into

<baz>
     <bar value=”10text=”Hello World/>
</baz>

And add more changes…

The advantage of XSLT over doing transformations in imperative code, is that adding more transformations to the document doesn’t make the XSLT more complex, just longer. For example, I could move the foo element inside another element by adding a new matching template.  The original parts of the template stay unchanged. 

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

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

        <xsl:template match="foo">
           <xsl:element name="bar">
                <xsl:apply-templates select="* | @* | node()" />
           </xsl:element>
        </xsl:template>

        <!-- Additional template that does not change previous -->
        <xsl:template match="baz">
            <xsl:element name="splitz">
               <xsl:copy>
                 <xsl:apply-templates select="* | @* | node()" />
              </xsl:copy>
            </xsl:element>
        </xsl:template>

</xsl:stylesheet>

With imperative code, adding additional transformations often requires doing refactoring of existing code.  Due to XSLT being from a more functional/declarative heritage, it tends to stay cleaner when you add more to it.

Just because I can…

And for those of you love JSON too much to ever go near XSLT, below is some sample code that takes a JSON version of my sample document and applies the XSLT transform to it using the XML support in JSON.NET.

Starting with this JSON

{
    "baz": {
        "foo": {
            "value": "10",
            "text": "HelloWorld"
        }
    }
}

we end up with

{
    "splitz": {
        "baz": {
            "bar": {
                "value": "10",
                "text": "Hello World"
            }
        }
    }
}

Here’s the code I used to do this.  Please don’t take this suggestion too seriously.  I suspect the performance of this approach would be pretty horrific.  However, if you have a big chunk of JSON that you need to do a complex, offline transformation on, it might just prove useful.

var jdoc = JObject.Parse("{ 'baz' : { 'foo' : { 'value' : '10', 'text' : 'Hello World' } }}");
            
// Convert Json to XML
var doc = (XmlDocument)JsonConvert.DeserializeXmlNode(jdoc.ToString());

// Create XML document containing Xslt Transform
var transform = new XmlDocument();
transform.LoadXml(@"<?xml version='1.0' encoding='utf-8'?>
                    <xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' >
                        <xsl:output method='xml' indent='yes' omit-xml-declaration='yes' />

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

                        <xsl:template match='foo'>
                            <xsl:element name='bar'>
                                <xsl:apply-templates select='* | @* | node()' />
                            </xsl:element>
                        </xsl:template>
                        <xsl:template match='baz'>  
                            <xsl:element name='splitz'>
                                <xsl:copy>
                                    <xsl:apply-templates select='* | @* | node()' />
                                </xsl:copy>
                        </xsl:element></xsl:template>
                    </xsl:stylesheet>");

//Create compiled transform object that will actually do the transform.
var xslt = new XslCompiledTransform();
xslt.Load(transform.CreateNavigator());

// Transform our Xml-ified JSON
var outputDocument = new XmlDocument();
var stream = new MemoryStream();
xslt.Transform(doc, null, stream);
stream.Position = 0;
outputDocument.Load(stream);

// Convert back to JSON :-)
string jsonText = JsonConvert.SerializeXmlNode(outputDocument);

Duck-billed Platypus

Image credit: Transformer https://flic.kr/p/2LK2ph
Image credit: Platypus https://flic.kr/p/8tYptD

No Comments

Add a Comment

comments powered by Disqus