在Java开发中,我们经常会定义抽象类或接口作为返回类型,实际返回的数据会根据业务场景对应不同的子类实现。当使用Jackson将JSON字符串反序列化为这类抽象类型时,默认无法识别具体要实例化的子类,需要借助type字段来动态匹配对应的子类。下面介绍两种常用的实现方案。
方案一:使用@JsonTypeInfo和@JsonSubTypes注解
Jackson提供了原生的类型信息处理注解,可以直接在抽象类上配置,实现基于type字段的子类映射,这种方式配置简单,适合子类类型固定的场景。
1. 定义抽象类及子类
首先定义一个基础的抽象类,以及两个对应的子类:
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
// 配置类型识别规则,使用type字段作为类型标识,值为子类的类型名称
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
// 配置type字段值和子类的映射关系
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "dog"),
@JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public abstract class Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Dog extends Animal {
private String barkSound;
public String getBarkSound() {
return barkSound;
}
public void setBarkSound(String barkSound) {
this.barkSound = barkSound;
}
}
public class Cat extends Animal {
private String meowSound;
public String getMeowSound() {
return meowSound;
}
public void setMeowSound(String meowSound) {
this.meowSound = meowSound;
}
}
2. 测试反序列化
编写测试代码验证反序列化效果:
import com.fasterxml.jackson.databind.ObjectMapper;
public class Test {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
// 狗的JSON,type为dog
String dogJson = "{"type":"dog","name":"小黑","barkSound":"汪汪汪"}";
Animal dog = objectMapper.readValue(dogJson, Animal.class);
System.out.println(dog instanceof Dog); // 输出true
System.out.println(((Dog) dog).getBarkSound()); // 输出汪汪汪
// 猫的JSON,type为cat
String catJson = "{"type":"cat","name":"小白","meowSound":"喵喵喵"}";
Animal cat = objectMapper.readValue(catJson, Animal.class);
System.out.println(cat instanceof Cat); // 输出true
System.out.println(((Cat) cat).getMeowSound()); // 输出喵喵喵
}
}
方案二:自定义反序列化器
如果子类类型不固定,或者type字段的映射规则比较复杂,比如需要根据type字段的值查询数据库获取对应的子类,就可以使用自定义反序列化器实现更灵活的控制。
1. 自定义反序列化器实现
继承StdDeserializer类,重写deserialize方法,手动解析type字段并选择对应的子类实例化:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
public class AnimalDeserializer extends StdDeserializer<Animal> {
public AnimalDeserializer() {
super(Animal.class);
}
@Override
public Animal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);
// 获取type字段的值
JsonNode typeNode = node.get("type");
if (typeNode == null) {
throw new IOException("JSON中缺少type字段,无法识别子类类型");
}
String type = typeNode.asText();
// 根据type字段选择对应的子类
if ("dog".equals(type)) {
Dog dog = new Dog();
dog.setName(node.get("name").asText());
dog.setBarkSound(node.get("barkSound").asText());
return dog;
} else if ("cat".equals(type)) {
Cat cat = new Cat();
cat.setName(node.get("name").asText());
cat.setMeowSound(node.get("meowSound").asText());
return cat;
} else {
throw new IOException("未知的type类型:" + type);
}
}
}
2. 注册自定义反序列化器
在抽象类上添加@JsonDeserialize注解指定自定义反序列化器:
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonDeserialize(using = AnimalDeserializer.class)
public abstract class Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// Dog和Cat类定义和方案一相同,此处省略
3. 测试验证
使用和方案一相同的测试代码,同样可以正确反序列化为对应的子类实例。
两种方案对比
| 对比项 | 注解方案 | 自定义反序列化器方案 |
|---|---|---|
| 配置复杂度 | 低,只需添加注解 | 高,需要编写自定义类 |
| 灵活性 | 低,子类固定,映射规则简单 | 高,支持复杂映射逻辑 |
| 适用场景 | 子类类型固定,type映射规则简单 | 子类类型动态,映射规则复杂 |
注意事项
- 使用注解方案时,
@JsonSubTypes中的子类必须可以被Jackson实例化,也就是要有默认的无参构造方法,或者配置对应的构造器注解。 - 自定义反序列化器中如果需要处理更复杂的字段映射,可以借助
ObjectMapper的treeToValue方法将JsonNode转换为具体的子类对象,避免手动逐个字段赋值。 - 如果抽象类同时被多个字段使用,建议将类型映射规则统一维护,避免重复配置。