Jackson 多态反序列化:@JsonTypeInfo + @JsonSubTypes 实战讲解

在业务开发中,我们经常遇到这样的场景:
同一个字段,根据外部的 type 类型,结构完全不同。
例如:

{
  "type": "CAT",
  "data": {
    "name": "Tom",
    "color": "white"
  }
}

或者:

{
  "type": "DOG",
  "data": {
    "name": "Jack",
    "age": 3
  }
}

我们希望反序列化后:

  • 当 type = "CAT" → data 自动转成 Cat 类;
  • 当 type = "DOG" → data 自动转成 Dog 类。

这时,就可以用 Jackson 的 多态反序列化 注解:
@JsonTypeInfo 和 @JsonSubTypes。

✅ 一、字段级写法(推荐)

这种写法最常用于 请求体中某个字段结构可变 的场景,比如 data、content、payload 等字段。

@Data
public class AnimalRequest {

    private String type; // 👈 决定 data 结构的外部类型字段

    @JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,              // 用字符串标识类型
        include = JsonTypeInfo.As.EXTERNAL_PROPERTY, // 👈 type 在 data 外层
        property = "type"                        // 指定类型字段名
    )
    @JsonSubTypes({
        @JsonSubTypes.Type(value = Cat.class, name = "CAT"),
        @JsonSubTypes.Type(value = Dog.class, name = "DOG")
    })
    private Animal data; // 👈 不同 type 值对应不同子类
}

子类定义:

@Data
public abstract class Animal {
    private String name;
}

@Data
public class Cat extends Animal {
    private String color;
}

@Data
public class Dog extends Animal {
    private Integer age;
}

示例 JSON:

{
  "type": "CAT",
  "data": {
    "name": "Tom",
    "color": "white"
  }
}

反序列化效果:

AnimalRequest req = objectMapper.readValue(json, AnimalRequest.class);
System.out.println(req.getData().getClass().getSimpleName());
// 输出:Cat ✅

⚠️ 二、为什么用 EXTERNAL_PROPERTY?

这一点特别容易搞混。
include 决定了「类型标识」在 JSON 中的位置:

include 写法 含义 JSON 结构示例
PROPERTY 类型字段在对象内部 "data": {"type":"CAT","name":"Tom"}
EXTERNAL_PROPERTY 类型字段在对象外部(同级) "type":"CAT","data":{"name":"Tom"}

在我们这种结构下,type 与 data 是同级的,
因此必须用 EXTERNAL_PROPERTY。
如果错写成 PROPERTY,Jackson 会去 data 内部找 type,导致反序列化失败。

🧱 三、类级写法(另一种思路)

除了在字段上声明,也可以直接在父类上声明。
这种方式适合抽象类结构比较独立的场景,例如:

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY, // 此时 type 在对象内部
    property = "type"
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Cat.class, name = "CAT"),
    @JsonSubTypes.Type(value = Dog.class, name = "DOG")
})
public abstract class Animal {
    private String name;
}

对应的 JSON:

{
  "data": {
    "type": "CAT",
    "name": "Tom",
    "color": "white"
  }
}

这种写法定义一次,全局通用,但灵活性略低。

✅ 四、总结

对比项 字段级 类级
注解位置 字段上 父类上
适用范围 当前字段 所有子类
适合场景 请求体字段多态 通用模型继承
灵活性 ✅ 高 ❌ 较低

推荐做法:

  • 如果只有某个字段存在多态结构 → 放在字段上(EXTERNAL_PROPERTY)
  • 如果整个类体系都是多态结构 → 放在父类上(PROPERTY)

💬 一句话记忆

当类型标识字段在外层时,用 EXTERNAL_PROPERTY;
当类型字段在对象内部时,用 PROPERTY。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容