1

Problem

I am tasked with integrating an external system via their third party API. The data I receive are XML files, with a given structure, whereas the API expects JSON with a different structure. Parsing and generating either is not the problem on its own, but what is the best practice for converting such data. I have compiled the solutions I can think of below.

Solution 1

The first solution I came up with was creating both models separately and create a converter. This way the mapping is less of a problem. However, I have to recreate and maintain a domain model, that already exists. Reusing is no option because this domain model is twenty years old and not separateable from the business logic. Besides I feel I am violating the SOC principle, due to the converters need to know about the structure of the models.

Solution 2

Alternatively, I can create a single model and annotate it with the JAXB and the Jackson annotations. This approach provides a single model without the need of a converter. This reduces the maintenance for those classes. On the other hand, this will probably yield more glue-code to make up for the structural differences between both models.


Neither of those solutions appeals to me. But I am willing to accept one as my fate, if I have to. If there is any other way to solving this issue, I would be thankful to learn about it. Sources on examples for either approach are highly appreciated.

Community
  • 1
  • 1
tgr
  • 3,557
  • 4
  • 33
  • 63
  • How is Solution 2 possible if XML and JSON have different structures? – lexicore Apr 11 '18 at 18:15
  • There are ways to extract nested XML structures with XPATH for example, which would map different structures to a single model. – tgr Apr 12 '18 at 05:13

3 Answers3

2

Solution 2 is definitely more appealing. But the problem is that you say your XML and JSON have different structures. If there are structural differences, you have to compensate for them. I am not sure how it is possible with the single model, the compensation has to happen somehow.

So the whole point is that you need some kind of flexible approach to map between different structures. XSLT is a good instrument for this. So I'd suggest a different solution.

Create just one model matching your JSON, annotate it with Jackson annotations (or whatever you use for JSON). Also annotate it with JAXB annotations. So far it is like Solution 2. The problem is, however that the XML structure in this case is based on JSON structure and is not directly compatible with the incoming XML structure. To solve this problem, write an XSLT transformation which would transfrom incoming XML into JSON-based XML structure. Essentially:

XML (incoming)  
  -(XSLT)-> XML (JSON-based)
  -(JAXB)-> Java objects
  -(Jackson)->
JSON

XSLT is very powerful and flexible instrument for XML transformation. One important contra is that you'll probably need two XSLTs: forward and reverse. Otherwise it would be rather difficult to test.

An alternative which I saw a few times is to actually have two models and use something like Dozer to convert between them:

XML (incoming)  
  -(JAXB)-> Java objects (incoming XML model)
  -(Dozer)-> Java object (JSON-based model)
  -(Jackson)->
JSON

This might also work. You should have an XML Schema for the incoming XML so Java classes for the incoming XML model could be generated, so you don't have much maintenance overhead here. The problem is that (at least for my measures) Dozer and likes are much less flexible and powerful than XSLT. I think it is much easier to write an XSLT to transform between XML structures than using Dozer to transfrom between Java structures.

lexicore
  • 42,748
  • 17
  • 132
  • 221
1

XSLT provides an elegant solution to this kind of requirement. As an example, here's a sample transform which works on the XML provided on this page and converts it to the json given on the same page.

<?xml version="1.0" encoding="utf-8"?>    
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="text" />
    <xsl:strip-space elements="*" />
    <xsl:variable name="nl"><xsl:text>&#xa;</xsl:text></xsl:variable>
    <xsl:variable name="q">"</xsl:variable>
    <xsl:variable name="bInd" select="'  '" />

    <xsl:template match="glossary">
        <xsl:value-of select="concat('{', $nl, $bInd, $q, local-name(.), $q, ' : {')" />

        <xsl:apply-templates>
            <xsl:with-param name="ind" select="concat($bInd, $bInd)" />
        </xsl:apply-templates>

        <xsl:value-of select="concat($nl, $bInd, '}', $nl, '}', $nl)" />
    </xsl:template>

    <xsl:template match="title">
        <xsl:param name="ind" />
        <xsl:value-of select="concat($nl, $ind, '  ', $q, local-name(.), $q, ' : ', $q, ., $q)" />
    </xsl:template>

    <!-- Other text elements -->
    <xsl:template match="*">
        <xsl:param name="ind" />
        <xsl:value-of select="concat($nl, $ind, ', ', $q, local-name(.), $q, ' : ', $q, ., $q)" />
    </xsl:template>

    <xsl:template match="GlossEntry">
        <xsl:param name="ind" />
        <xsl:value-of select="concat($nl, $ind, $q, local-name(.), $q, ' : {')" />
        <xsl:value-of select="concat($nl, $ind, $bInd, '  ',$q, 'ID', $q, ' : ', $q, @ID , $q)" />
        <xsl:value-of select="concat($nl, $ind, $bInd, ', ', $q, 'SortAs', $q, ' : ', $q, @SortAs , $q)" />

        <xsl:apply-templates>
            <xsl:with-param name="ind" select="concat($ind, $bInd)" />
        </xsl:apply-templates>

        <xsl:value-of select="concat($nl, $ind, '}')" />
    </xsl:template>

    <xsl:template match="GlossDef">
        <xsl:param name="ind" />
        <xsl:value-of select="concat($nl, $ind, ', ', $q, local-name(.), $q, ' : {')" />
        <xsl:value-of select="concat($nl, $ind, $bInd, '  ', $q, 'para', $q, ' : ', $q, para, $q)" />
        <xsl:value-of select="concat($nl, $ind, $bInd, ', ', $q, 'GlossSeeAlso', $q, ' : [ ')" />

        <xsl:for-each select="GlossSeeAlso">
            <xsl:apply-templates select=".">
                <xsl:with-param name="pos" select="position()" />
            </xsl:apply-templates>
        </xsl:for-each>

        <xsl:value-of select="' ]'" />
        <xsl:value-of select="concat($nl, $ind, '}')" />
    </xsl:template>

    <xsl:template match="GlossSeeAlso">
        <xsl:param name="pos" />

        <xsl:if test="$pos > 1">
            <xsl:value-of select="', '" />
        </xsl:if>

        <xsl:value-of select="concat($q, @OtherTerm, $q)" />
    </xsl:template>

    <xsl:template match="GlossSee">
        <xsl:param name="ind" />
        <xsl:value-of select="concat($nl, $ind, ', ', $q, local-name(.), $q, ' : ', $q, @OtherTerm, $q)" />
    </xsl:template>

    <xsl:template match="GlossDiv | GlossList">
        <xsl:param name="ind" />
        <xsl:value-of select="concat($nl, $ind, ', ', $q, local-name(.), $q, ' : {')" />

        <xsl:apply-templates>
            <xsl:with-param name="ind" select="concat($ind, $bInd)" />
        </xsl:apply-templates>

        <xsl:value-of select="concat($nl, $ind, '}')" />
    </xsl:template>
</xsl:transform>

This sample transform is XSLT 1.0. You can test it by adding a reference to it in the XML file, and loading the XML file in a web browser.

For example, if the above transform is stored in test.xsl, place the XML in test.xml with the transform referenced like this:

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="test.xsl"?>

<glossary>
    <title>example glossary</title>

    <GlossDiv>
        <title>S</title>

        <GlossList>
            <GlossEntry ID="SGML" SortAs="SGML">
                <GlossTerm>Standard Generalized Markup Language</GlossTerm>
                <Acronym>SGML</Acronym>
                <Abbrev>ISO 8879:1986</Abbrev>

                <GlossDef>
                    <para>A meta-markup language, used to create markup languages such as DocBook.</para>
                    <GlossSeeAlso OtherTerm="GML" />
                    <GlossSeeAlso OtherTerm="XML" />
                </GlossDef>

                <GlossSee OtherTerm="markup" />
            </GlossEntry>
        </GlossList>
    </GlossDiv>
</glossary>
Pylot
  • 31
  • 3
  • 1
    I like the direction but I won't do this way. This XSLT does two things at once: transform the structure of XML and convert XML to JSON. It will be hard to implement and to maintain. And it's also not really necessary to do both things at once. If XML matches JSON structure, it can be converted to JSON automatically. So why not use XSLT to "simply" convert "incoming XML" to "XML that matches JSON structure"? – lexicore Apr 11 '18 at 18:34
-1

I suggestion is , you can create model for xml structure and convert model to json. there are third party jars which will convert model to json and vice versa.

Also checkout this link - Quickest way to convert XML to JSON in Java

Ramesh Fadatare
  • 561
  • 4
  • 12