为什么Jackson序列化为XML的命名空间结构不正确

huangapple go评论70阅读模式
英文:

Why are the namespaces Jackson serializes into XML structured incorrectly

问题

以下是翻译好的部分:

我正尝试使用Jackson的XmlMapper将对象序列化为XML。我希望在适当的位置使用命名空间。然而,在我序列化对象时,每个属性都有一个命名空间 - 直接属于我尝试序列化的类的属性将具有xmlns="",而引用类上的每个属性都包含它们自己的命名空间前缀(而不是整个引用类的单个前缀)。

我正在使用Jackson 2.9.8。

我想要序列化的主要类:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
        "name",
        "objectDescription",
        "exampleFieldClass"
})
@XmlRootElement(name = "ExampleClass", namespace = "urn:ExampleClass.dto")
public class ExampleClass
{
    // 类的字段和方法
}

一个特殊类型:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ExampleFieldClass", namespace = "urn:ExampleFieldClass.dto", propOrder = {
        "id", "dtoType"
})
public class ExampleFieldClass {
    // 类的字段和方法
}

XmlMapper的配置和一个测试:

public class ExampleTest {
    // 测试方法和配置
}

实际的XML输出如下:

<?xml version='1.0' encoding='UTF-8'?>
<ExampleClass xmlns="urn:ExampleClass.dto">
    <name xmlns="">name</name>
    <objectDescription xmlns="">description</objectDescription>
    <exampleFieldClass xmlns="">
        <wstxns1:id xmlns:wstxns1="urn:ExampleFieldClass.dto">id</wstxns1:id>
        <wstxns2:dtoType xmlns:wstxns2="urn:ExampleFieldClass.dto">dtoType</wstxns2:dtoType>
    </exampleFieldClass>
</ExampleClass>

命名空间不应该在每个属性上定义,也不应该有定义为空字符串的命名空间。

需要注意的是:如果我将属性注释更改为@XmlElement,那么空命名空间xmlns=""就不会存在;然而,它将无法正确处理JaxbElement(空值与null值)。

有人知道我做错了什么吗?

英文:

I am trying to serialize an object into XML using Jackson's XmlMapper. I want it to have namespaces in appropriate areas. When I serialize the object, however, every attribute is having a namespace - attributes that are directly part of the class I'm trying to serialize will have an xmlns="", and each attribute on referenced classes contains their own namespace prefix (rather than a single prefix for the whole referenced class).

I'm using Jackson 2.9.8

The main class I want to serialize:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = &quot;&quot;, propOrder = {
        &quot;name&quot;,
        &quot;objectDescription&quot;,
        &quot;exampleFieldClass&quot;
})
@XmlRootElement(name = &quot;ExampleClass&quot;, namespace = &quot;urn:ExampleClass.dto&quot;)
public class ExampleClass
{

    @XmlElementRef(name = &quot;name&quot;, namespace = &quot;urn:ExampleClass.dto&quot;, type = JAXBElement.class, required = false)
    protected JAXBElement&lt;String&gt; name;
    @XmlElementRef(name = &quot;objectDescription&quot;, namespace = &quot;urn:ExampleClass.dto&quot;, type = JAXBElement.class, required = false)
    protected JAXBElement&lt;String&gt; objectDescription;
    @XmlElementRef(name = &quot;exampleFieldClass&quot;, namespace = &quot;urn:ExampleFieldClass.dto&quot;, type = JAXBElement.class, required = false)
    protected JAXBElement&lt;ExampleFieldClass&gt; ExampleFieldClass;

    public JAXBElement&lt;String&gt; getName() {
        return name;
    }

    public void setName(JAXBElement&lt;String&gt; name) {
        this.name = name;
    }

    public JAXBElement&lt;String&gt; getObjectDescription() {
        return objectDescription;
    }

    public void setObjectDescription(JAXBElement&lt;String&gt; objectDescription) {
        this.objectDescription = objectDescription;
    }

    public JAXBElement&lt;ExampleFieldClass&gt; getExampleFieldClass() {
        return ExampleFieldClass;
    }

    public void setExampleFieldClass(JAXBElement&lt;ExampleFieldClass&gt; ExampleFieldClass) {
        this.ExampleFieldClass = ExampleFieldClass;
    }
}

A special type:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = &quot;ExampleFieldClass&quot;, namespace = &quot;urn:ExampleFieldClass.dto&quot;, propOrder = {
        &quot;id&quot;, &quot;dtoType&quot;
})
public class ExampleFieldClass {

    @XmlElement(name = &quot;id&quot;, namespace = &quot;urn:ExampleFieldClass.dto&quot;)
    protected String id;
    @XmlElement(name = &quot;dtoType&quot;, namespace = &quot;urn:ExampleFieldClass.dto&quot;)
    protected String dtoType;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getDtoType() {
        return dtoType;
    }

    public void setDtoType(String dtoType) {
        this.dtoType = dtoType;
    }
}

The XmlMapper config and a test:

public class ExampleTest {

    private final ExampleObjectFactory factory = new ExampleObjectFactory();

    @Test
    public void testSerialize() throws Exception {
        XmlMapper mapper = getXmlMapper();

        ExampleFieldClass exampleFieldClass = new ExampleFieldClass();
        exampleFieldClass.setDtoType(&quot;dtoType&quot;);
        exampleFieldClass.setId(&quot;id&quot;);

        ExampleClass exampleClass = new ExampleClass();
        exampleClass.setName(factory.createExampleClassName(&quot;name&quot;));
        exampleClass.setObjectDescription(factory.createExampleClassObjectDescription(&quot;description&quot;));
        exampleClass.setExampleFieldClass(factory.createExampleClassExampleFieldClass(exampleFieldClass));

        String serialized = mapper.writeValueAsString(exampleClass);
        System.out.println(serialized);
    }

    private XmlMapper getXmlMapper() {
        JacksonXmlModule module = new JacksonXmlModule();
        module.addSerializer(JAXBElement.class, new JsonSerializer&lt;JAXBElement&gt;() {
            @Override
            public void serialize(JAXBElement value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                if (value.isNil()) {
                    gen.writeNull();
                } else {
                    gen.writeObject(value.getValue());
                }
            }
        });

        XmlMapper objectMapper = new XmlMapper(module);
        objectMapper.registerModule(new JaxbAnnotationModule());

        objectMapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        return objectMapper;
    }

    @XmlRegistry
    public static class ExampleObjectFactory {

        @XmlElementDecl(namespace = &quot;urn:ExampleClass.dto&quot;, name = &quot;name&quot;, scope = ExampleClass.class)
        public JAXBElement&lt;String&gt; createExampleClassName(String value) {
            QName _ExampleClassName_QNAME = new QName(&quot;urn:ExampleClass.dto&quot;, &quot;name&quot;);
            return new JAXBElement&lt;&gt;(_ExampleClassName_QNAME, String.class, ExampleClass.class, value);
        }

        @XmlElementDecl(namespace = &quot;urn:ExampleClass.dto&quot;, name = &quot;objectDescription&quot;, scope = ExampleClass.class)
        public JAXBElement&lt;String&gt; createExampleClassObjectDescription(String value) {
            QName _ExampleClassObjectDescription_QNAME = new QName(&quot;urn:ExampleClass.dto&quot;, &quot;objectDescription&quot;);
            return new JAXBElement&lt;&gt;(_ExampleClassObjectDescription_QNAME, String.class, ExampleClass.class, value);
        }

        @XmlElementDecl(namespace = &quot;urn:ExampleFieldClass.dto&quot;, name = &quot;ExampleFieldClass&quot;, scope = ExampleFieldClass.class)
        public JAXBElement&lt;ExampleFieldClass&gt; createExampleClassExampleFieldClass(ExampleFieldClass value) {
            QName _ExampleClassExampleFieldClass_QNAME = new QName(&quot;urn:ExampleFieldClass.dto&quot;, &quot;ExampleFieldClass&quot;);
            return new JAXBElement&lt;&gt;(_ExampleClassExampleFieldClass_QNAME, ExampleFieldClass.class, ExampleClass.class, value);
        }
    }
}

The actual XML output is this:

&lt;?xml version=&#39;1.0&#39; encoding=&#39;UTF-8&#39;?&gt;
&lt;ExampleClass xmlns=&quot;urn:ExampleClass.dto&quot;&gt;
    &lt;name xmlns=&quot;&quot;&gt;name&lt;/name&gt;
    &lt;objectDescription xmlns=&quot;&quot;&gt;description&lt;/objectDescription&gt;
    &lt;exampleFieldClass xmlns=&quot;&quot;&gt;
        &lt;wstxns1:id xmlns:wstxns1=&quot;urn:ExampleFieldClass.dto&quot;&gt;id&lt;/wstxns1:id&gt;
        &lt;wstxns2:dtoType xmlns:wstxns2=&quot;urn:ExampleFieldClass.dto&quot;&gt;dtoType&lt;/wstxns2:dtoType&gt;
    &lt;/exampleFieldClass&gt;
&lt;/ExampleClass&gt;

The namespaces shouldn't be defined on every attribute, nor should there be ones defined as an empty String.

Something to note: the empty namespaces xmlns="" will not be there if I change the attribute annotations to @XmlElement; however, it will no longer handle JaxbElement properly (empty vs null).

Does anyone know what I'm doing wrong for this?

答案1

得分: 1

如果你改变了ExampleClass的定义

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "", propOrder = {
            "name",
            "objectDescription",
            "exampleFieldClass"
    })
    @XmlRootElement(name = "ExampleClass", namespace = "urn:ExampleClass.dto")
    public class ExampleClass
    {
        
        @XmlElement(name = "name", namespace = "urn:ExampleClass.dto", required = false)
        protected String name;
        
        @XmlElement(name = "objectDescription", namespace = "urn:ExampleClass.dto", required = false)
        protected String objectDescription;
        
        @XmlElement(name = "exampleFieldClass", namespace = "urn:ExampleFieldClass.dto", required = false)
        protected ExampleFieldClass ExampleFieldClass;
    
    }

并且改变了创建ExampleClass实例的代码

        XmlMapper mapper = getXmlMapper();

        ExampleFieldClass exampleFieldClass = new ExampleFieldClass();
        exampleFieldClass.setDtoType("dtoType");
        exampleFieldClass.setId("id");

        ExampleClass exampleClass = new ExampleClass();
        exampleClass.setName("name");
        exampleClass.setObjectDescription("description");
        exampleClass.setExampleFieldClass(exampleFieldClass);

        String serialized = mapper.writeValueAsString(exampleClass);

输出将会是

    <?xml version='1.0' encoding='UTF-8'?>
    <ExampleClass xmlns="urn:ExampleClass.dto">
      <name>name</name>
      <objectDescription>description</objectDescription>
      <wstxns1:exampleFieldClass xmlns:wstxns1="urn:ExampleFieldClass.dto">
        <wstxns1:id>id</wstxns1:id>
        <wstxns1:dtoType>dtoType</wstxns1:dtoType>
      </wstxns1:exampleFieldClass>
    </ExampleClass>
英文:

If you change definition of ExampleClass:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = &quot;&quot;, propOrder = {
&quot;name&quot;,
&quot;objectDescription&quot;,
&quot;exampleFieldClass&quot;
})
@XmlRootElement(name = &quot;ExampleClass&quot;, namespace = &quot;urn:ExampleClass.dto&quot;)
public class ExampleClass
{
@XmlElement(name = &quot;name&quot;, namespace = &quot;urn:ExampleClass.dto&quot;, required = false)
protected String name;
@XmlElement(name = &quot;objectDescription&quot;, namespace = &quot;urn:ExampleClass.dto&quot;, required = false)
protected String objectDescription;
@XmlElement(name = &quot;exampleFieldClass&quot;, namespace = &quot;urn:ExampleFieldClass.dto&quot;, required = false)
protected ExampleFieldClass ExampleFieldClass;
}

And change creation of ExampleClass instance:

    XmlMapper mapper = getXmlMapper();
ExampleFieldClass exampleFieldClass = new ExampleFieldClass();
exampleFieldClass.setDtoType(&quot;dtoType&quot;);
exampleFieldClass.setId(&quot;id&quot;);
ExampleClass exampleClass = new ExampleClass();
exampleClass.setName(&quot;name&quot;);
exampleClass.setObjectDescription(&quot;description&quot;);
exampleClass.setExampleFieldClass(exampleFieldClass);
String serialized = mapper.writeValueAsString(exampleClass);

Output will be:

&lt;?xml version=&#39;1.0&#39; encoding=&#39;UTF-8&#39;?&gt;
&lt;ExampleClass xmlns=&quot;urn:ExampleClass.dto&quot;&gt;
&lt;name&gt;name&lt;/name&gt;
&lt;objectDescription&gt;description&lt;/objectDescription&gt;
&lt;wstxns1:exampleFieldClass xmlns:wstxns1=&quot;urn:ExampleFieldClass.dto&quot;&gt;
&lt;wstxns1:id&gt;id&lt;/wstxns1:id&gt;
&lt;wstxns1:dtoType&gt;dtoType&lt;/wstxns1:dtoType&gt;
&lt;/wstxns1:exampleFieldClass&gt;
&lt;/ExampleClass&gt;

答案2

得分: 1

好的,这是翻译好的内容:

好的,在深入研究了 jaxb/jackson/spring/google... 很多之后,我找到了 XmlJaxbAnnotationIntrospector。这个类将查看在被序列化的任何类上的 JAXB 注解,以确定诸如简单名称、命名空间、属性顺序等信息。问题出现在 findNamespacefindRootName 方法中。

findNamespace(Annotated ann):这里有两个问题。首先,在尝试确定类的命名空间时,它只会查找 XmlRootElement 注解。它不会查找其他可能包含命名空间的注解(在我的情况下是 XmlType)。同样的问题也适用于 XmlElementRef 注解 - 当前的方法将查找 XmlElementXmlAttribute 以确定命名空间;它不会查找 XmlElementRef

findRootName(Annotated ann):与 findNamespace 的问题类似,这在尝试查找命名空间时只会查看 XmlRootElement。对于我们的情况,再次地,这并不存在,我们需要它查看 XmlType 注解。

我提出的解决方案,请告诉我是否有理由不应该这样做,是继承自 XmlJaxbAnnotationIntrospector,创建我自己的实现,覆盖 findNamespacefindRootName 方法。每个方法本质上是相同的,只是还加入了对 XmlType 注解和 XmlElementRef 注解的搜索。一旦创建了这个实现,只需使用以下方式将它添加到对象映射器中:

objectMapper.setAnnotationIntrospector(new CustomXmlJaxbAnnotationIntrospector());

需要注意的是,对于可能阅读此内容并遇到我遇到的其他问题的人,propOrder 默认情况下似乎不处理继承。因此,如果你有一个具有 propOrder 的父类,这些属性将不会首先出现在你的序列化 xml 中。为了解决这个问题,在那个 CustomXmlJaxbAnnotationInstrospector 类中,还要覆盖 findSerializationPropertyOrder(AnnotatedClass annotatedClass) 方法 - 在这里,可以循环遍历超类(直到 Object.class),并将每组属性前置到一个包含它们所有的新列表/String[] 中。

英文:

Ok, after digging into jaxb/jackson/spring/google... a lot... I found the XmlJaxbAnnotationIntrospector. This class will look at the JAXB annotations on whatever classes are being serialized to determine information such as simple name, namespace, propOrder, etc. The problem lies in the findNamespace and findRootName methods.

findNamespace(Annotated ann): there's 2 problems here. The first is, when it tries to determine the namespace of a Class, it will only look for the XmlRootElement annotation. It will not look for other annotations that may contain namespace (in my case, XmlType). The same issue applies for the XmlElementRef annotation - the current method will look for XmlElement and XmlAttribute to determine the namespace; it does not look for XmlElementRef.

findRootName(Annotated ann): similar to the findNamespace issue, this will only look at the XmlRootElement when trying to find the namespace. For our situation, again, that does not exist and we need it to look at the XmlType annotation.

The solution that I have, and please let me know if there's a reason it should not be doing this, was to extend from the XmlJaxbAnnotationIntrospector, creating my own implementation that overrides findNamespace and findRootName. Each method is essentially the same, except that it also adds the search for the XmlType annotation and XmlElementRef annotation. Once this is created, just add it to the object mapper using:

objectMapper.setAnnotationIntrospector(new CustomXmlJaxbAnnotationIntrospector());

Something to note, for anyone who might read this and have an additional issue that I ran into, propOrder doesn't seem to handle inheritance by default. Therefore, if you have a parent class that has a propOrder, those attributes will not be first in your serialized xml. To get around this, in that CustomXmlJaxbAnnotationInstrospector class, also override the findSerializationPropertyOrder(AnnotatedClass annotatedClass) - in here, you can loop through the super classes (up until Object.class), and prepend each set of props to a new list/String[] that will contain them all.

huangapple
  • 本文由 发表于 2020年10月10日 07:12:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/64288390.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定