本文探讨了如何使用Jackson库将包含多态元素的列表序列化为xml,并为列表中的每个不同类型元素生成其对应的类名作为XML标签。针对Jackson默认行为无法满足此需求的问题,文章详细介绍了通过实现自定义jsonSerializer来精确控制XML输出结构的方法。同时,文章也指出了这种自定义方案可能带来的局限性,特别是在牺牲Jackson内置的多态类型信息处理能力(如JsonTypeInfo)方面的权衡。
理解Jackson XML序列化多态列表的挑战
在使用jackson将Java对象序列化为xml时,当一个类包含一个多态类型的列表(例如list
考虑以下Java类结构:
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import java.util.ArrayList; import java.util.List; // 抽象基类 abstract class Animal {} // 子类,并指定XML根元素名称 @JacksonXmlRootElement(localName = "Dog") class Dog extends Animal {} @JacksonXmlRootElement(localName = "Cat") class Cat extends Animal {} // 包含多态列表的类 @JacksonXmlRootElement(localName = "Zoo") public class Zoo { @JacksonXmlProperty @JacksonXmlElementWrapper(useWrapping = false) // 禁用列表包装,避免额外的<animals>标签 List<Animal> animals = new ArrayList<>(); }
当我们尝试序列化一个Zoo实例时:
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.dataformat.xml.XmlMapper; public class SerializationDemo { public static void main(String[] args) throws JsonProcessingException { Zoo zoo = new Zoo(); zoo.animals.add(new Dog()); zoo.animals.add(new Cat()); zoo.animals.add(new Dog()); XmlMapper mapper = new XmlMapper(); String xml = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(zoo); System.out.println(xml); } }
我们期望的XML输出是这样的:
<Zoo> <Dog/> <Cat/> <Dog/> </Zoo>
然而,实际输出却是:
<Zoo> <animals/> <animals/> <animals/> </Zoo>
尽管我们使用了@JacksonXmlRootElement来指定Dog和Cat的根元素名,并在Zoo类的animals字段上使用了@JacksonXmlElementWrapper(useWrapping = false)来移除默认的
解决方案:自定义JsonSerializer
为了实现期望的XML输出,我们需要通过实现一个自定义的JsonSerializer来精确控制序列化过程。这种方法允许我们遍历列表中的每个元素,并根据其运行时类型动态地生成相应的XML标签。
首先,我们需要在Zoo类上应用@JsonSerialize注解,指定使用我们自定义的序列化器:
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import java.util.ArrayList; import java.util.List; // Animal, Dog, Cat 类保持不变 @JsonSerialize(using = ZooSerializer.class) // 指定使用自定义序列化器 @JacksonXmlRootElement(localName = "Zoo") public class Zoo { @JacksonXmlProperty @JacksonXmlElementWrapper(useWrapping = false) List<Animal> animals = new ArrayList<>(); }
接下来,创建自定义的ZooSerializer类:
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import java.io.IOException; public class ZooSerializer extends StdSerializer<Zoo> { // 构造函数 public ZooSerializer() { this(NULL); } public ZooSerializer(Class<Zoo> t) { super(t); } @Override public void serialize(Zoo zoo, JsonGenerator jg, SerializerProvider sp) throws IOException { jg.writeStartObject(); // 写入 <Zoo> 标签的开始 for (Animal animal : zoo.animals) { // 获取动物的简单类名(如 "Dog", "Cat") String tagName = animal.getClass().getSimpleName(); // 写入一个空字段,其名称即为动物的类名,从而生成 <Dog/> 或 <Cat/> 标签 jg.writeNullField(tagName); } jg.writeEndObject(); // 写入 </Zoo> 标签的结束 } }
在ZooSerializer的serialize方法中,我们执行以下操作:
- jg.writeStartObject():这会生成Zoo对象的开始标签,即
。 - 遍历zoo.animals列表中的每个Animal对象。
- 对于每个animal,我们通过animal.getClass().getSimpleName()获取其运行时类型的简单名称(例如,如果animal是Dog的实例,则获取”Dog”)。
- jg.writeNullField(tagName):这是关键步骤。它会创建一个XML元素,其名称为tagName,并且由于它是“null field”,Jackson会将其序列化为自闭合标签,例如
或 。 - jg.writeEndObject():这会生成Zoo对象的结束标签,即。
使用此自定义序列化器后,再次运行序列化代码,将得到期望的XML输出:
<Zoo> <Dog/> <Cat/> <Dog/> </Zoo>
注意事项与权衡
虽然自定义JsonSerializer能够精确地控制XML输出格式,但这种方法也存在一些重要的局限性:
-
失去Jackson内置多态处理优势: Jackson提供了@JsonTypeInfo和@JsonSubTypes等注解,用于在JSON/XML中嵌入类型信息,从而实现自动的多态序列化和反序列化。使用自定义序列化器意味着你放弃了这些内置的便利性。例如,如果你需要将上述XML反序列化回Java对象,Jackson将无法自动识别
和 标签对应的具体子类,因为自定义序列化器没有写入任何类型元数据。 -
需要自定义反序列化器: 如果你的应用需要支持XML到Java对象的反序列化,那么你将不得不编写一个对应的自定义JsonDeserializer来解析这些特定格式的XML标签,并将其映射回正确的Dog或Cat对象。这增加了代码的复杂性和维护成本。
-
通用性降低: 这种自定义序列化器是为特定的Zoo类和其animals列表量身定制的。如果你的应用程序中有许多类似的多态列表需要处理,你可能需要为每个列表编写类似的自定义序列化器,或者尝试设计一个更通用的解决方案(这通常会更复杂)。
总结
当Jackson的默认XML序列化行为无法满足特定需求,例如需要为多态列表中的每个元素生成其运行时类型作为XML标签时,实现自定义JsonSerializer是一种有效的解决方案。它提供了对XML输出格式的精细控制。然而,开发者在采用此方法前应充分权衡其利弊,特别是要考虑到它会牺牲Jackson内置的多态类型处理能力,并可能需要额外开发配套的自定义反序列化器。在大多数情况下,如果XML标签名称的精确控制不是强制要求,并且需要支持双向序列化/反序列化,那么优先考虑使用@JsonTypeInfo和@JsonSubTypes来利用Jackson强大的多态处理能力会是更优的选择。