
本文详解如何在 jackson 自定义 `stdserializer` 中正确序列化多态集合中的子类实例,解决因误用 `writenullfield` 导致子类字段丢失的问题,并提供可运行的 xml 序列化示例与反序列化注意事项。
在使用 Jackson(尤其是 XmlMapper)处理多态对象(如 List
正确的做法是:在自定义 serialize() 方法中,对每个 Animal 实例调用 JsonGenerator#writeObjectField(String fieldName, Object value)。该方法会自动触发 Jackson 对 value(即 Dog 实例)的标准序列化流程——包括其 public 字段(breed, age)、注解感知(如 @JsonProperty)、以及类型信息(若已配置 @JsonTypeInfo)。
以下是修复后的 ZooSerializer 完整实现:
public class ZooSerializer extends StdSerializer{ public ZooSerializer() { this(null); } public ZooSerializer(Class t) { super(t); } @Override public void serialize(Zoo zoo, JsonGenerator jg, SerializerProvider sp) throws IOException { jg.writeStartObject(); for (Animal animal : zoo.animals) { String typeName = animal.getClass().getSimpleName(); jg.writeObjectField(typeName, animal); // ✅ 关键:委托 Jackson 序列化子类实例 } jg.writeEndObject(); } }
配合以下测试代码:
@Test
public void testZooSerialization() throws JsonProcessingException {
XmlMapper xmlMapper = new XmlMapper();
// 启用默认 getter/setter 支持(若字段非 public,需额外配置)
xmlMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
Zoo zoo = new Zoo();
zoo.animals.add(new Dog("Collie", "6"));
zoo.animals.add(new Cat("Siamese", "3")); // 假设 Cat 类结构类似
String xml = xmlMapper.writerWithDefaultPrettyPrinter().writeValueAsString(zoo);
System.out.println(xml);
}输出将正确包含子类字段:
Collie 6 Siamese 3
⚠️ 重要注意事项:
-
反序列化需配套自定义反序列化器:当前 ZooSerializer 仅处理序列化;若需从 XML 反序列化回 Zoo,必须实现 JsonDeserializer
并结合 @JsonTypeInfo(如 use = Id.NAME)与 @JsonSubTypes 显式声明子类映射,否则 Jackson 无法推断 Dog/Cat 的具体类型。 - 字段可见性:确保 Dog 等子类字段为 public,或通过 XmlMapper 配置 setVisibility() 启用字段访问(如上例所示),否则字段将被忽略。
- 避免重复序列化逻辑:切勿在 serialize() 中手动遍历子类字段并调用 writeStringField() 等——这会绕过 Jackson 的类型处理、注解解析和空值策略,丧失框架优势。
综上,writeObjectField() 是连接自定义容器序列化与标准子类序列化的关键桥梁。它既保留了对容器结构(如按类名生成字段)的完全控制,又复用了 Jackson 成熟的 POJO 序列化能力,是处理多态 XML 序列化的推荐实践。









