将非匹配数据模型从XML转换为JSON的最佳实践

时间:2018-04-11 09:50:48

标签: java json xml jackson jaxb

问题

我的任务是通过第三方API集成外部系统。我收到的数据是XML文件,具有给定的结构,而API期望JSON具有不同的结构。解析和生成要么本身不是问题,但转换此类数据的最佳做法是什么。我编写了下面我能想到的解决方案。

解决方案1 ​​

我提出的第一个解决方案是分别创建两个模型并创建转换器。这样,映射不是问题。但是,我必须重新创建并维护已存在的域模型。重用是没有选择的,因为这个域模型已有二十年历史,并且不能与业务逻辑分离。此外我觉得我违反了SOC principle,因为转换器需要了解模型的结构。

解决方案2

或者,我可以使用JAXB和Jackson注释创建单个模型并对其进行注释。该方法提供单个模型而无需转换器。这减少了这些类的维护。另一方面,这可能会产生更多胶水代码,以弥补两种模型之间的结构差异。

这些解决方案都没有吸引我。但我愿意接受一个人作为我的命运,如果必须的话。如果还有其他方法可以解决这个问题,我将非常感谢您了解它。关于这两种方法的例子的来源都受到高度赞赏。

3 个答案:

答案 0 :(得分:1)

XSLT为这种需求提供了一个优雅的解决方案。例如,这是一个示例转换,它适用于this page上提供的XML,并将其转换为同一页面上给出的json。

<?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>

此示例转换是XSLT 1.0。您可以通过在XML文件中添加对它的引用并在Web浏览器中加载XML文件来测试它。

例如,如果上述转换存储在test.xsl中,请将XML放在test.xml中,其变换方式如下:

<?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>

答案 1 :(得分:1)

解决方案2 绝对更具吸引力。但问题是你说你的XML和JSON有不同的结构。如果存在结构差异,则必须对其进行补偿。我不确定单一模型是如何实现的,补偿必须以某种方式发生。

所以重点是你需要某种灵活的方法来映射不同的结构。 XSLT是一个很好的工具。所以我建议采用不同的解决方案。

只创建一个与您的JSON匹配的模型,使用Jackson注释(或任何您用于JSON的注释)对其进行注释。还使用JAXB注释对其进行注释。到目前为止它就像解决方案2.但问题是,在这种情况下,XML结构基于JSON结构,并且不与传入的XML结构直接兼容。要解决此问题,请编写一个XSLT转换,它将传入的XML转换为基于JSON的XML结构。基本上:

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

XSLT是非常强大且灵活的XML转换工具。一个重要的反对意见是你可能需要两个XSLT:正向和反向。否则将很难测试。

我看过几次的替代方案是实际上有两个模型并使用像Dozer这样的东西在它们之间进行转换:

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

这也可行。您应该有一个XML Schema用于传入的XML,因此可以生成传入XML模型的Java类,因此这里没有太多的维护开销。问题是(至少对我来说)Dozer和喜欢的东西比XSLT更不灵活和强大。我认为编写XSLT以在XML结构之间进行转换比使用Dozer在Java结构之间进行转换要容易得多。

答案 2 :(得分:-1)

我的建议是,你可以为xml结构创建模型并将模型转换为json。有第三方罐子将模型转换为json,反之亦然。

另请查看此链接 - Quickest way to convert XML to JSON in Java