在业务开发中,我们经常遇到这样的场景:
同一个字段,根据外部的 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。