为什么 Jackson 序列化为 XML 的命名空间结构不正确
Why are the namespaces Jackson serializes into XML structured incorrectly
我正在尝试使用 Jackson 的 XmlMapper 将对象序列化为 XML。我希望它在适当的区域有名称空间。但是,当我序列化对象时,每个属性都有一个命名空间 - 直接属于 class 我尝试序列化的属性将有一个 xmlns="",并且引用的每个属性 classes 包含它们自己的命名空间前缀(而不是整个引用的单个前缀 class)。
我正在使用 Jackson 2.9.8
主要class我要连载:
@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(空与空)。
有人知道我做错了什么吗?
如果您更改 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>
好的,在深入研究 jaxb/jackson/spring/google 之后...很多...我找到了 XmlJaxbAnnotationIntrospector
。 class 将查看正在序列化的任何 classes 上的 JAXB 注释,以确定简单名称、名称空间、propOrder 等信息。问题在于 findNamespace
和 findRootName
方法。
findNamespace(Annotated ann)
: 这里有两个问题。第一个是,当它试图确定 Class 的命名空间时,它只会查找 XmlRootElement
注释。它不会寻找其他可能包含名称空间的注释(在我的例子中,XmlType
)。同样的问题适用于 XmlElementRef
注释——当前方法将查找 XmlElement
和 XmlAttribute
来确定名称空间;它不寻找 XmlElementRef
.
findRootName(Annotated ann)
:类似于findNamespace
问题,这只会在尝试查找命名空间时查看XmlRootElement
。同样,对于我们的情况,它不存在,我们需要它来查看 XmlType
注释。
我的解决方案,如果有理由不应该这样做,请告诉我,是从 XmlJaxbAnnotationIntrospector
扩展,创建我自己的覆盖 findNamespace
的实现和findRootName
。每个方法本质上是相同的,除了它还添加了对 XmlType
注释和 XmlElementRef
注释的搜索。创建后,只需使用以下命令将其添加到对象映射器:
objectMapper.setAnnotationIntrospector(new CustomXmlJaxbAnnotationIntrospector());
需要注意的是,对于可能阅读本文并遇到我 运行 遇到的其他问题的任何人,propOrder
默认情况下似乎不处理继承。因此,如果您的父 class 具有 propOrder
,那么这些属性将不会在您的序列化 xml 中排在第一位。为了解决这个问题,在 CustomXmlJaxbAnnotationInstrospector
class 中,还要覆盖 findSerializationPropertyOrder(AnnotatedClass annotatedClass)
- 在这里,您可以循环遍历超级 classes(直到 Object.class ), 并将每组道具添加到一个新的 list/String[] 中,它将包含所有道具。
我正在尝试使用 Jackson 的 XmlMapper 将对象序列化为 XML。我希望它在适当的区域有名称空间。但是,当我序列化对象时,每个属性都有一个命名空间 - 直接属于 class 我尝试序列化的属性将有一个 xmlns="",并且引用的每个属性 classes 包含它们自己的命名空间前缀(而不是整个引用的单个前缀 class)。
我正在使用 Jackson 2.9.8
主要class我要连载:
@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(空与空)。
有人知道我做错了什么吗?
如果您更改 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>
好的,在深入研究 jaxb/jackson/spring/google 之后...很多...我找到了 XmlJaxbAnnotationIntrospector
。 class 将查看正在序列化的任何 classes 上的 JAXB 注释,以确定简单名称、名称空间、propOrder 等信息。问题在于 findNamespace
和 findRootName
方法。
findNamespace(Annotated ann)
: 这里有两个问题。第一个是,当它试图确定 Class 的命名空间时,它只会查找 XmlRootElement
注释。它不会寻找其他可能包含名称空间的注释(在我的例子中,XmlType
)。同样的问题适用于 XmlElementRef
注释——当前方法将查找 XmlElement
和 XmlAttribute
来确定名称空间;它不寻找 XmlElementRef
.
findRootName(Annotated ann)
:类似于findNamespace
问题,这只会在尝试查找命名空间时查看XmlRootElement
。同样,对于我们的情况,它不存在,我们需要它来查看 XmlType
注释。
我的解决方案,如果有理由不应该这样做,请告诉我,是从 XmlJaxbAnnotationIntrospector
扩展,创建我自己的覆盖 findNamespace
的实现和findRootName
。每个方法本质上是相同的,除了它还添加了对 XmlType
注释和 XmlElementRef
注释的搜索。创建后,只需使用以下命令将其添加到对象映射器:
objectMapper.setAnnotationIntrospector(new CustomXmlJaxbAnnotationIntrospector());
需要注意的是,对于可能阅读本文并遇到我 运行 遇到的其他问题的任何人,propOrder
默认情况下似乎不处理继承。因此,如果您的父 class 具有 propOrder
,那么这些属性将不会在您的序列化 xml 中排在第一位。为了解决这个问题,在 CustomXmlJaxbAnnotationInstrospector
class 中,还要覆盖 findSerializationPropertyOrder(AnnotatedClass annotatedClass)
- 在这里,您可以循环遍历超级 classes(直到 Object.class ), 并将每组道具添加到一个新的 list/String[] 中,它将包含所有道具。