在使用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 {} // 子类Dog,期望序列化为<Dog/> @JacksonXmlRootElement(localName = "Dog") class Dog extends Animal {} // 子类Cat,期望序列化为<Cat/> @JacksonXmlRootElement(localName = "Cat") class Cat extends Animal {} // 包含多态列表的Zoo类 @JacksonXmlRootElement(localName = "Zoo") public class Zoo { @JacksonXmlProperty @JacksonXmlElementWrapper(useWrapping = false) // 尝试不包装列表 List<Animal> animals = new ArrayList<>(); }
当尝试序列化一个包含Dog和Cat实例的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("实际输出:\n" + xml); } }
期望的XML输出是:
<Zoo> <Dog/> <Cat/> <Dog/> </Zoo>
然而,实际输出可能更接近:
<Zoo> <animals/> <animals/> <animals/> </Zoo>
这表明默认的Jackson XML序列化器并未将列表中的每个元素识别为其具体的子类型并生成相应的根元素标签。
为了实现对XML输出的精细控制,特别是当需要根据多态列表元素的实际类型生成特定的XML标签时,最直接有效的方法是实现一个自定义的JsonSerializer。通过自定义序列化器,我们可以完全控制JsonGenerator,从而手动构建所需的XML结构。
保持Animal、Dog和Cat类的定义不变。对于Zoo类,我们需要通过@JsonSerialize注解指定一个自定义的序列化器:
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import java.util.ArrayList; import java.util.List; // 抽象父类 public abstract class Animal {} // 子类Dog public class Dog extends Animal {} // 子类Cat public class Cat extends Animal {} // Zoo类,指定自定义序列化器 @JsonSerialize(using = ZooSerializer.class) // 关键:使用自定义序列化器 @JacksonXmlRootElement(localName = "Zoo") // Zoo根元素标签 public class Zoo { // 列表字段,不需要JacksonXmlProperty或JacksonXmlElementWrapper,因为自定义序列化器会处理 List<Animal> animals = new ArrayList<>(); // 示例:添加构造函数或方法方便测试 public Zoo() {} public List<Animal> getAnimals() { return animals; } }
请注意,Zoo类中的animals字段不再需要@JacksonXmlProperty或@JacksonXmlElementWrapper注解,因为自定义的ZooSerializer将完全接管Zoo对象的序列化过程。
自定义序列化器需要继承com.fasterxml.jackson.databind.ser.std.StdSerializer并重写serialize方法。在这个方法中,我们将遍历Zoo对象中的animals列表,并为每个Animal子类对象生成一个以其类名命名的空标签。
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 { // 开始写入Zoo对象的根元素(由@JacksonXmlRootElement(localName = "Zoo")处理) // 在这里,我们只需要处理其内部的animals列表 // 对于XML,Jackson的JsonGenerator会将writeStartObject()映射到根元素标签, // 但由于Zoo类本身有@JacksonXmlRootElement,这里的writeStartObject()会是其内部内容。 // 为了确保Zoo标签内部直接是Dog/Cat,我们直接在jg上操作,而不是先writeStartObject() // 因为ZooSerializer是针对Zoo对象整体的,它会负责整个Zoo标签的内容。 // 通常,@JsonSerialize在类级别,意味着你控制的是整个类的序列化。 // Jackson的XmlMapper在处理@JsonSerialize注解时,会先写入被注解类的根元素, // 然后调用自定义序列化器的serialize方法来填充该根元素内部的内容。 // 因此,我们不需要在这里手动写入<Zoo>标签。 // 我们需要做的是,在Zoo标签内部,为每个Animal写入其对应的标签。 // 遍历animals列表 for (Animal animal : zoo.getAnimals()) { // 获取动物的简单类名作为XML标签名 (例如 "Dog", "Cat") String tagName = animal.getClass().getSimpleName(); // 写入一个空字段,Jackson的XmlMapper会将其解释为一个空标签,如 <Dog/> 或 <Cat/> jg.writeNullField(tagName); } // 不需要jg.writeEndObject(),因为自定义序列化器是为整个Zoo对象服务的, // Jackson的XmlMapper会在调用完serialize方法后自动关闭Zoo标签。 } }
在serialize方法中,我们遍历Zoo对象中的animals列表。对于列表中的每个Animal对象,我们通过animal.getClass().getSimpleName()获取其具体的类名(例如"Dog"或"Cat"),然后使用jg.writeNullField(tagName)方法来写入一个以该类名为标签的空XML元素。
现在,使用XmlMapper来序列化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("最终输出:\n" + xml); } }
运行上述代码,你将获得期望的XML输出:
<Zoo> <Dog/> <Cat/> <Dog/> </Zoo>
使用自定义JsonSerializer虽然能够实现精确的XML输出控制,但它也带来了一些重要的权衡:
失去Jackson自动多态处理的优势:
代码维护成本: 当你的数据模型发生变化(例如添加新的Animal子类),你需要手动修改ZooSerializer以处理新的类型,而不是仅仅添加新的@JsonSubTypes注解。
适用场景: 这种方法最适用于以下场景:
Jackson提供了@JsonTypeInfo和@JsonSubTypes注解来处理多态序列化和反序列化。通常,它们会在XML中添加一个属性(如@class)来指示对象的实际类型,或者将子类序列化为带有类型信息的包装元素。例如:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT) @JsonSubTypes({ @JsonSubTypes.Type(value = Dog.class, name = "Dog"), @JsonSubTypes.Type(value = Cat.class, name = "Cat") }) abstract class Animal {} // 这里的Animal需要是可序列化的,或者在父类上定义多态信息 // Zoo类中animals列表的定义可能需要调整,或者直接在Animal父类上定义多态信息 // ...
这种标准方法生成的XML可能看起来像这样(具体取决于include和use的设置):
<Zoo> <animals> <Dog/> </animals> <animals> <Cat/> </animals> <animals> <Dog/> </animals> </Zoo>
或者,如果@JsonTypeInfo配置为在根元素上包含类型信息,并且@JacksonXmlElementWrapper(useWrapping = false):
<Zoo> <Dog type="Dog"/> <Cat type="Cat"/> <Dog type="Dog"/> </Zoo>
这与本教程中期望的
当Jackson的默认XML序列化行为无法满足为多态列表元素生成特定、简洁的XML标签(即以子类名作为标签)时,实现自定义JsonSerializer提供了一种强大而灵活的解决方案。这种方法允许开发者精确控制XML输出的每一个细节,从而生成符合严格规范的XML文档。然而,开发者需要清楚地认识到这种方式带来的权衡,特别是需要手动处理反序列化以及放弃Jackson自动多态机制的便利性。在选择此方案时,务必权衡其带来的控制能力与维护成本。
以上就是Jackson XML多态列表元素自定义标签序列化的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 //m.sbmmt.com/ All Rights Reserved | php.cn | 湘ICP备2023035733号