Jackson 自定义序列化器中正确嵌套序列化子类字段的完整教程

本文详解如何在 jackson 自定义 `jsonserializer` 中正确序列化多态集合中的子类实例(如 `dog`),避免字段丢失问题,通过 `writeobjectfield` 实现自动委托序列化,并补充反序列化注意事项。

在使用 Jackson(尤其是 XmlMapper)处理多态对象(如 List)时,若为容器类(如 Zoo)编写自定义序列化器,直接调用 writeNullField() 或手动写入字段名会导致

子类实际数据丢失——因为这仅生成空标签(如 ),并未触发 Jackson 对 Dog 实例本身的序列化逻辑。

根本原因在于:jg.writeNullField("Dog") 仅向输出流写入一个名为 "Dog" 的 null 值字段,完全绕过了 Jackson 的类型发现、注解解析与字段遍历机制;而单独序列化 dog 时,Jackson 默认使用标准 Bean 序列化器,自动读取 @JsonProperty、可见性规则及字段值,因此能正常输出 和 。

✅ 正确做法是:在自定义序列化器中,将子对象交还给 Jackson 的默认序列化流程。使用 JsonGenerator.writeObjectField(String fieldName, Object value) 方法——它会自动根据 value 的实际类型(如 Dog),查找并委托其对应的序列化器(可能是默认 BeanSerializer 或其他自定义器),从而完整输出所有可序列化字段。

以下是修复后的 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();
            // ✅ 关键:委托 Jackson 自动序列化 animal 实例
            jg.writeObjectField(typeName, animal);
        }
        jg.writeEndObject();
    }
}

配合示例测试代码运行后,输出即变为符合预期的嵌套 XML:


  
    Collie
    6
  

⚠️ 重要注意事项:

  • 反序列化需配套自定义 JsonDeserializer:当前序列化输出无类型标识(如 @class 字段或 XML 属性),Jackson 无法自动推断 标签应反序列化为 Dog 实例。若需反序列化,必须实现 ZooDeserializer,解析元素名(如 "Dog")并手动构造对应子类对象。
  • 字段可见性需保持一致:确保 Dog.breed/Dog.age 满足 Jackson 默认可见性规则(如 public 字段,或配置 MapperFeature.INFER_PROPERTY_MUTATORS = true)。
  • 避免重复注解冲突:若已在 Animal 或子类上使用 @JsonTypeInfo,则无需手动按类名写字段,应优先采用 Jackson 内置多态支持,而非自定义序列化器。

总结:自定义序列化器不是“从零手写 XML”,而是协调与委托。善用 writeObjectField、writeObject 等方法,让 Jackson 复用其成熟的序列化管道,既能保证字段完整性,又能兼容注解、泛型与多态机制。