
在实际的web服务交互中,我们经常会遇到json数组中包含多种类型对象的情况,这些对象可能共享一个共同的基类,但部分元素是其子类的实例,拥有基类所没有的额外属性。例如,一个车辆列表可能既包含普通轿车(car),也包含卡车(truck),其中truck是car的子类,并拥有如maxload、clearance等特有属性。
当尝试使用Jackson库将这样的JSON数组直接反序列化为List<Car>时,如果JSON中包含Truck的实例,Jackson会尝试将Truck特有的字段(如maxLoad)映射到Car类中。由于Car类中没有这些字段,Jackson会抛出com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException异常,提示无法识别的属性。
考虑以下Java类定义和对应的JSON数据:
Java类定义:
public class Car {
private String make;
private String model;
private short year;
private String bodyStyle;
private String engineType;
private int horsepower;
// Getters and Setters
public String getMake() { return make; }
public void setMake(String make) { this.make = make; }
public String getModel() { return model; }
public void setModel(String model) { this.model = model; }
public short getYear() { return year; }
public void setYear(short year) { this.year = year; }
public String getBodyStyle() { return bodyStyle; }
public void setBodyStyle(String bodyStyle) { this.bodyStyle = bodyStyle; }
public String getEngineType() { return engineType; }
public void setEngineType(String engineType) { this.engineType = engineType; }
public int getHorsepower() { return horsepower; }
public void setHorsepower(int horsepower) { this.horsepower = horsepower; }
@Override
public String toString() {
return "Car{" +
"make='" + make + '\'' +
", model='" + model + '\'' +
", year=" + year +
", bodyStyle='" + bodyStyle + '\'' +
", engineType='" + engineType + '\'' +
", horsepower=" + horsepower +
'}';
}
}
public class Truck extends Car {
private double maxLoad;
private double clearance;
// Getters and Setters
public double getMaxLoad() { return maxLoad; }
public void setMaxLoad(double maxLoad) { this.maxLoad = maxLoad; }
public double getClearance() { return clearance; }
public void setClearance(double clearance) { this.clearance = clearance; }
@Override
public String toString() {
return "Truck{" +
"make='" + getMake() + '\'' +
", model='" + getModel() + '\'' +
", year=" + getYear() +
", bodyStyle='" + getBodyStyle() + '\'' +
", engineType='" + getEngineType() + '\'' +
", horsepower=" + getHorsepower() +
", maxLoad=" + maxLoad +
", clearance=" + clearance +
'}';
}
}JSON数据示例 (cars.json):
[
{
"make": "Ford",
"model": "Focus",
"year": 2018,
"engineType": "T4",
"bodyStyle": "hatchback",
"horsepower": 225
},
{
"make": "Toyota",
"model": "Prius",
"year": 2021,
"engineType": "T4",
"bodyStyle": "hatchback",
"horsepower": 121
},
{
"make": "Toyota",
"model": "RAV4",
"year": 2020,
"engineType": "V6",
"bodyStyle": "SUV",
"horsepower": 230
},
{
"make": "Toyota",
"model": "Tacoma",
"year": 2021,
"engineType": "V6",
"bodyStyle": "pickup",
"horsepower": 278,
"maxLoad": 1050,
"clearance": 9.4
},
{
"make": "Ford",
"model": "T150",
"year": 2017,
"horsepower": 450,
"bodyStyle": "pickup",
"maxLoad": 2320,
"clearance": 8.4
}
]直接使用mapper.readValue(src, new TypeReference<List<Car>>() {})进行反序列化,会遇到上述的UnrecognizedPropertyException。虽然将List<Car>替换为List<Truck>可以避免异常,但这只会成功反序列化Truck实例,而普通Car实例则会被忽略或无法正确处理,导致数据丢失。
Jackson提供了强大的多态反序列化机制,通过在基类上添加@JsonTypeInfo和@JsonSubTypes注解,可以指导Jackson在运行时根据JSON内容的特征来推断并实例化正确的子类对象。
1. @JsonTypeInfo注解详解
该注解用于指定多态类型信息的处理方式。对于本场景,最合适的策略是JsonTypeInfo.Id.DEDUCTION。
2. @JsonSubTypes注解详解
该注解用于注册基类的所有已知子类型。Jackson在进行类型推断时,会参考这里注册的子类型列表。
带注解的Java类定义:
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
// 在基类Car上添加多态注解
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION, defaultImpl = Car.class)
@JsonSubTypes(@JsonSubTypes.Type(value = Truck.class))
public class Car {
private String make;
private String model;
private short year;
private String bodyStyle;
private String engineType;
private int horsepower;
// 构造函数 (可选,Jackson通常使用默认无参构造函数)
public Car() {}
// Getters and Setters
public String getMake() { return make; }
public void setMake(String make) { this.make = make; }
public String getModel() { return model; }
public void setModel(String model) { this.model = model; }
public short getYear() { return year; }
public void setYear(short year) { this.year = year; }
public String getBodyStyle() { return bodyStyle; }
public void setBodyStyle(String bodyStyle) { this.bodyStyle = bodyStyle; }
public String getEngineType() { return engineType; }
public void setEngineType(String engineType) { this.engineType = engineType; }
public int getHorsepower() { return horsepower; }
public void setHorsepower(int horsepower) { this.horsepower = horsepower; }
@Override
public String toString() {
return "Car{" +
"make='" + make + '\'' +
", model='" + model + '\'' +
", year=" + year +
", bodyStyle='" + bodyStyle + '\'' +
", engineType='" + engineType + '\'' +
", horsepower=" + horsepower +
'}';
}
}
public class Truck extends Car {
private double maxLoad;
private double clearance;
// 构造函数
public Truck() {}
// Getters and Setters
public double getMaxLoad() { return maxLoad; }
public void setMaxLoad(double maxLoad) { this.maxLoad = maxLoad; }
public double getClearance() { return clearance; }
public void setClearance(double clearance) { this.clearance = clearance; }
@Override
public String toString() {
return "Truck{" +
"make='" + getMake() + '\'' +
", model='" + getModel() + '\'' +
", year=" + getYear() +
", bodyStyle='" + getBodyStyle() + '\'' +
", engineType='" + getEngineType() + '\'' +
", horsepower=" + getHorsepower() +
", maxLoad=" + maxLoad +
", clearance=" + clearance +
'}';
}
}下面是使用上述带注解的类和JSON数据进行反序列化的完整测试代码:
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class Test {
private static final ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) {
// 假设 "cars.json" 文件位于 classpath 中
InputStream src = Test.class.getClassLoader().getResourceAsStream("cars.json");
if (src == null) {
System.err.println("Error: cars.json not found in classpath.");
return;
}
try {
// 反序列化为 List<Car>,Jackson将根据注解自动处理多态
List<Car> cars = mapper.readValue(src, new TypeReference<List<Car>>() {});
System.out.println("Deserialization successful. Objects in the list:");
for (Car car : cars) {
System.out.println(" Object type: " + car.getClass().getName());
System.out.println(" Details: " + car.toString()); // 使用toString打印详细信息
// 可以进行类型检查并访问子类特有属性
if (car instanceof Truck) {
Truck truck = (Truck) car;
System.out.println(" (As Truck) Max Load: " + truck.getMaxLoad() + ", Clearance: " + truck.getClearance());
}
System.out.println("------------------------------------");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}运行上述测试程序,你将看到如下输出(或类似输出):
Deserialization successful. Objects in the list:
Object type: com.yourpackage.Car
Details: Car{make='Ford', model='Focus', year=2018, bodyStyle='hatchback', engineType='T4', horsepower=225}
------------------------------------
Object type: com.yourpackage.Car
Details: Car{make='Toyota', model='Prius', year=2021, bodyStyle='hatchback', engineType='T4', horsepower=121}
------------------------------------
Object type: com.yourpackage.Car
Details: Car{make='Toyota', model='RAV4', year=2020, bodyStyle='SUV', engineType='V6', horsepower=230}
------------------------------------
Object type: com.yourpackage.Truck
Details: Truck{make='Toyota', model='Tacoma', year=2021, bodyStyle='pickup', engineType='V6', horsepower=278, maxLoad=1050.0, clearance=9.4}
(As Truck) Max Load: 1050.0, Clearance: 9.4
------------------------------------
Object type: com.yourpackage.Truck
Details: Truck{make='Ford', model='T150', year=2017, bodyStyle='pickup', engineType='null', horsepower=450, maxLoad=2320.0, clearance=8.4}
(As Truck) Max Load: 2320.0, Clearance: 8.4
------------------------------------从输出可以看出,Jackson成功地将JSON数组中的Car对象反序列化为Car实例,将包含maxLoad和clearance字段的JSON对象反序列化为Truck实例。这是因为:
这种机制使得我们能够在一个List<Car>中同时持有Car和Truck的实例,并且在需要时可以安全地进行向下转型来访问子类特有属性。
以上就是Jackson多态反序列化:优雅处理包含基类与子类对象的JSON数组的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号