首页 > Java > java教程 > 正文

Jackson多态反序列化:优雅处理包含基类与子类对象的JSON数组

聖光之護
发布: 2025-09-24 12:53:21
原创
643人浏览过

Jackson多态反序列化:优雅处理包含基类与子类对象的JSON数组

本文详细阐述了如何使用Jackson库处理包含基类和其子类对象的JSON数组,并将其反序列化为基类类型的列表。通过在基类上应用@JsonTypeInfo和@JsonSubTypes注解,结合JsonTypeInfo.Id.DEDUCTION策略和defaultImpl配置,Jackson能够智能识别并实例化不同类型的对象,从而避免UnrecognizedPropertyException,实现灵活的多态数据映射。

问题背景:多态JSON数组的反序列化挑战

在实际的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提供了强大的多态反序列化机制,通过在基类上添加@JsonTypeInfo和@JsonSubTypes注解,可以指导Jackson在运行时根据JSON内容的特征来推断并实例化正确的子类对象。

1. @JsonTypeInfo注解详解

该注解用于指定多态类型信息的处理方式。对于本场景,最合适的策略是JsonTypeInfo.Id.DEDUCTION。

序列猴子开放平台
序列猴子开放平台

具有长序列、多模态、单模型、大数据等特点的超大规模语言模型

序列猴子开放平台 0
查看详情 序列猴子开放平台
  • use = JsonTypeInfo.Id.DEDUCTION: Jackson会尝试通过检查JSON对象中存在的字段来“推断”其实际类型。它会遍历所有已注册的子类型,如果某个子类型的所有特有字段都在JSON中出现,并且基类的字段也匹配,则认为该JSON对象是该子类型的实例。
  • defaultImpl = Car.class: 这是一个关键配置。当Jackson无法通过DEDUCTION策略将JSON对象匹配到任何已注册的子类型时(例如,它就是一个纯粹的基类对象,不包含任何子类特有字段),它会回退到使用defaultImpl指定的类来实例化对象。在本例中,即Car.class。

2. @JsonSubTypes注解详解

该注解用于注册基类的所有已知子类型。Jackson在进行类型推断时,会参考这里注册的子类型列表。

  • @JsonSubTypes.Type(value = Truck.class): 注册Truck类作为Car的一个子类型。如果有多个子类型,可以添加多个@JsonSubTypes.Type条目。

带注解的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实例。这是因为:

  • 当Jackson遇到一个JSON对象,它会检查是否包含Truck特有的字段(如maxLoad)。如果存在,它会推断该对象是一个Truck,并实例化Truck类。
  • 如果JSON对象不包含任何Truck特有的字段,Jackson无法将其推断为Truck。此时,defaultImpl = Car.class发挥作用,Jackson会回退并实例化一个Car对象。

这种机制使得我们能够在一个List<Car>中同时持有Car和Truck的实例,并且在需要时可以安全地进行向下转型来访问子类特有属性。

注意事项与最佳实践

  1. DEDUCTION策略的适用性: JsonTypeInfo.Id.DEDUCTION最适用于子类拥有明确且不与基类或其他子类重叠的独特字段的情况。如果子类之间没有足够的字段差异,或者子类特有字段可能缺失,DEDUCTION可能无法准确推断类型。
  2. 注册所有子类型: 务必通过@JsonSubTypes注解注册所有可能的子类型。如果未注册某个子类型,Jackson将无法识别它,并可能将其作为defaultImpl处理。
  3. 其他类型推断策略: 如果DEDUCTION不适合你的场景,Jackson还提供了其他JsonTypeInfo.Id策略:
    • Id.CLASS:JSON中需要包含一个特殊字段(通常是@class),其值是类的完全限定名。
    • Id.NAME:JSON中需要包含一个特殊字段(通常是@type),其值是子类型在@JsonSubTypes中定义的逻辑名称。 这些策略要求JSON数据中明确包含类型信息,提供了更精确的控制,但同时也增加了

以上就是Jackson多态反序列化:优雅处理包含基类与子类对象的JSON数组的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号