【问题标题】:Why are the namespaces Jackson serializes into XML structured incorrectly为什么 Jackson 序列化为 XML 的命名空间结构不正确
【发布时间】:2020-10-09 23:12:22
【问题描述】:

我正在尝试使用 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
{

    @XmlElementRef(name = "name", namespace = "urn:ExampleClass.dto", type = JAXBElement.class, required = false)
    protected JAXBElement<String> name;
    @XmlElementRef(name = "objectDescription", namespace = "urn:ExampleClass.dto", type = JAXBElement.class, required = false)
    protected JAXBElement<String> objectDescription;
    @XmlElementRef(name = "exampleFieldClass", namespace = "urn:ExampleFieldClass.dto", type = JAXBElement.class, required = false)
    protected JAXBElement<ExampleFieldClass> ExampleFieldClass;

    public JAXBElement<String> getName() {
        return name;
    }

    public void setName(JAXBElement<String> name) {
        this.name = name;
    }

    public JAXBElement<String> getObjectDescription() {
        return objectDescription;
    }

    public void setObjectDescription(JAXBElement<String> objectDescription) {
        this.objectDescription = objectDescription;
    }

    public JAXBElement<ExampleFieldClass> getExampleFieldClass() {
        return ExampleFieldClass;
    }

    public void setExampleFieldClass(JAXBElement<ExampleFieldClass> ExampleFieldClass) {
        this.ExampleFieldClass = ExampleFieldClass;
    }
}

一种特殊类型:

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

    @XmlElement(name = "id", namespace = "urn:ExampleFieldClass.dto")
    protected String id;
    @XmlElement(name = "dtoType", namespace = "urn:ExampleFieldClass.dto")
    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;
    }
}

XmlMapper 配置和测试:

public class ExampleTest {

    private final ExampleObjectFactory factory = new ExampleObjectFactory();

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

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

        ExampleClass exampleClass = new ExampleClass();
        exampleClass.setName(factory.createExampleClassName("name"));
        exampleClass.setObjectDescription(factory.createExampleClassObjectDescription("description"));
        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<JAXBElement>() {
            @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 = "urn:ExampleClass.dto", name = "name", scope = ExampleClass.class)
        public JAXBElement<String> createExampleClassName(String value) {
            QName _ExampleClassName_QNAME = new QName("urn:ExampleClass.dto", "name");
            return new JAXBElement<>(_ExampleClassName_QNAME, String.class, ExampleClass.class, value);
        }

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

        @XmlElementDecl(namespace = "urn:ExampleFieldClass.dto", name = "ExampleFieldClass", scope = ExampleFieldClass.class)
        public JAXBElement<ExampleFieldClass> createExampleClassExampleFieldClass(ExampleFieldClass value) {
            QName _ExampleClassExampleFieldClass_QNAME = new QName("urn:ExampleFieldClass.dto", "ExampleFieldClass");
            return new JAXBElement<>(_ExampleClassExampleFieldClass_QNAME, ExampleFieldClass.class, ExampleClass.class, value);
        }
    }
}

实际的 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(空与空)。

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

【问题讨论】:

  • 好吧...我设法至少简化了一些事情。 2个问题:1.我是否编辑问题并替换所有内容? 2. 我是否包括进口?
  • 1) 是的。 --- 2) 没有必要,因为您已经命名了您使用的库,但为了完整起见,您可以命名。

标签: java xml serialization jackson jaxb


【解决方案1】:

好的,在深入研究 jaxb/jackson/spring/google... 很多... 我找到了XmlJaxbAnnotationIntrospector。此类将查看正在序列化的任何类上的 JAXB 注释以确定简单名称、命名空间、propOrder 等信息。问题在于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),并将每组道具添加到新列表/字符串 [ ] 将包含所有内容。

【讨论】:

    【解决方案2】:

    如果你改变 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>
    

    【讨论】:

    • 很遗憾,我现在无法对此进行测试。但是,这是我尝试过的,问题是它不再处理 null 和 JaxbElement(null) 之间的区别。所以未定义与空值。反序列化时,两者都将被解释为纯 null。
    • 是的,这种方法无法区分 null 和未定义
    猜你喜欢
    • 2013-05-02
    • 2019-10-12
    • 2015-01-23
    • 1970-01-01
    • 1970-01-01
    • 2019-12-15
    • 1970-01-01
    • 1970-01-01
    • 2014-11-17
    相关资源
    最近更新 更多