首页 > Java > java教程 > 正文

Jackson处理包含多类型对象的JSON数组:实现多态反序列化到基类列表

聖光之護
发布: 2025-09-24 17:19:01
原创
772人浏览过

Jackson处理包含多类型对象的JSON数组:实现多态反序列化到基类列表

本教程详细讲解如何使用Jackson库处理包含基类和子类对象的JSON数组,并将其反序列化为基类类型的列表。针对UnrecognizedPropertyException问题,我们将介绍如何通过在基类上使用@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)和@JsonSubTypes注解,实现基于属性推断的多态反序列化,从而正确地将不同类型的对象映射到统一的基类列表中。

1. 问题背景与挑战

在实际开发中,我们经常会遇到需要从web服务接收json数据,其中包含一个对象数组,而这些对象可能属于不同的类,但它们都继承自一个共同的基类。例如,我们有一个car基类和一个truck子类,truck类除了继承car的属性外,还包含一些特有的属性,如maxload和clearance。我们的目标是将一个包含car和truck实例的json数组反序列化成一个list<car>。

示例JSON数据:

[
  {
    "make": "Ford",
    "model": "Focus",
    "year": 2018,
    "engineType": "T4",
    "bodyStyle": "hatchback",
    "horsepower": 225
  },
  {
    "make": "Toyota",
    "model": "Tacoma",
    "year": 2021,
    "engineType": "V6",
    "bodyStyle": "pickup",
    "horsepower": 278,
    "maxLoad": 1050,
    "clearance": 9.4
  }
]
登录后复制

初始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 omitted for brevity
}

public class Truck extends Car {
    private double  maxLoad;
    private double  clearance;
    // Getters and setters omitted for brevity
}
登录后复制

当我们尝试使用Jackson的ObjectMapper直接将上述JSON反序列化为List<Car>时,会遇到com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException异常。这是因为Jackson在尝试将包含maxLoad和clearance属性的JSON对象映射到Car类时,发现Car类中没有这些属性,从而抛出异常。虽然将目标类型改为List<Truck>可以避免异常,但这会导致所有对象都被尝试反序列化为Truck,并且无法正确处理纯Car类型的对象。

2. Jackson多态反序列化机制

为了解决这个问题,我们需要利用Jackson的多态反序列化(Polymorphic Deserialization)机制。Jackson允许我们在没有显式类型标识符的情况下,通过检查JSON对象的属性来推断其具体类型。这可以通过在基类上使用@JsonTypeInfo和@JsonSubTypes注解来实现。

3. 解决方案:使用@JsonTypeInfo.Id.DEDUCTION

核心思想是在基类Car上添加Jackson注解,告知它如何识别和处理其子类型。

3.1 注解配置

在Car类上添加@JsonTypeInfo和@JsonSubTypes注解:

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION, defaultImpl = Car.class)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Truck.class)
    // 如果有其他子类,也在此处添加,例如:
    // @JsonSubTypes.Type(value = Sedan.class),
    // @JsonSubTypes.Type(value = SUV.class)
})
public class Car {
    private String make;
    private String model;
    private short  year;
    private String bodyStyle;
    private String engineType;
    private int    horsepower;

    // 构造函数、Getters和Setters
    public Car() {} // 必须有无参构造函数
    // ... (省略Getters和Setters)

    @Override
    public String toString() {
        return "Car{" +
               "make='" + make + '\'' +
               ", model='" + model + '\'' +
               ", year=" + year +
               ", bodyStyle='" + bodyStyle + '\'' +
               ", engineType='" + engineType + '\'' +
               ", horsepower=" + horsepower +
               '}';
    }
}
登录后复制

Truck类保持不变,因为它已经继承了Car:

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

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

序列猴子开放平台 0
查看详情 序列猴子开放平台
public class Truck extends Car {
    private double  maxLoad;
    private double  clearance;

    // 构造函数、Getters和Setters
    public Truck() {} // 必须有无参构造函数
    // ... (省略Getters和Setters)

    @Override
    public String toString() {
        return "Truck{" +
               super.toString() + // 调用父类的toString
               ", maxLoad=" + maxLoad +
               ", clearance=" + clearance +
               '}';
    }
}
登录后复制

3.2 注解详解

  • @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION, defaultImpl = Car.class):

    • use = JsonTypeInfo.Id.DEDUCTION: 这是实现基于属性推断的关键。Jackson会尝试通过检查JSON对象的属性来推断其具体类型。例如,如果JSON对象包含maxLoad属性,Jackson会猜测它可能是一个Truck。
    • defaultImpl = Car.class: 这是一个重要的回退机制。如果Jackson无法根据@JsonSubTypes中定义的子类型推断出匹配的类型(例如,JSON对象只包含Car类的属性,或者包含未知属性但又不符合任何子类型),它将默认使用Car.class进行反序列化。这确保了纯Car对象或不完全匹配任何子类的对象也能被正确处理。
  • @JsonSubTypes({@JsonSubTypes.Type(value = Truck.class)}):

    • 这个注解用于声明Car的所有已知子类型。Jackson在执行DEDUCTION策略时,会根据这些声明的子类型来尝试匹配JSON对象的属性。value = Truck.class明确告诉Jackson,Truck是Car的一个可能的子类型。

4. 完整的示例代码

以下是一个完整的Java示例,演示如何使用上述注解进行多态反序列化:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.core.type.TypeReference;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

// Car.java (如上所示,包含Jackson注解和Getters/Setters/toString)
// Truck.java (如上所示,包含Getters/Setters/toString)

public class PolymorphicDeserializationDemo {

    private static final ObjectMapper mapper = new ObjectMapper();

    public static void main(String[] args) {
        // 模拟从文件或网络读取JSON
        String jsonInput = getJsonString(); // 获取JSON字符串

        try {
            // 使用TypeReference进行反序列化到List<Car>
            List<Car> cars = mapper.readValue(jsonInput, new TypeReference<List<Car>>() {});

            System.out.println("成功反序列化以下对象:");
            for (Car car : cars) {
                System.out.println("  类型: " + car.getClass().getSimpleName() + ", 数据: " + car);
                if (car instanceof Truck) {
                    Truck truck = (Truck) car;
                    System.out.println("    (卡车特有属性) Max Load: " + truck.getMaxLoad() + ", Clearance: " + truck.getClearance());
                }
            }
        } catch (IOException e) {
            System.err.println("反序列化失败: " + e.getMessage());
            e.printStackTrace();
        }
    }

    // 模拟获取JSON字符串的方法
    private static String getJsonString() {
        return "[ " +
               "    {" +
               "        \"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" +
               "    }   " +
               " ]";
    }
}
登录后复制

运行上述代码,你将看到输出中正确识别了Car和Truck实例,并将它们都存储在List<Car>中:

成功反序列化以下对象:
  类型: Car, 数据: Car{make='Ford', model='Focus', year=2018, bodyStyle='hatchback', engineType='T4', horsepower=225}
  类型: Car, 数据: Car{make='Toyota', model='Prius', year=2021, bodyStyle='hatchback', engineType='T4', horsepower=121}
  类型: Car, 数据: Car{make='Toyota', model='RAV4', year=2020, bodyStyle='SUV', engineType='V6', horsepower=230}
  类型: Truck, 数据: Truck{Car{make='Toyota', model='Tacoma', year=2021, bodyStyle='pickup', engineType='V6', horsepower=278}, maxLoad=1050.0, clearance=9.4}
    (卡车特有属性) Max Load: 1050.0, Clearance: 9.4
  类型: Truck, 数据: Truck{Car{make='Ford', model='T150', year=2017, bodyStyle='pickup', engineType='null', horsepower=450}, maxLoad=2320.0, clearance=8.4}
    (卡车特有属性) Max Load: 2320.0, Clearance: 8.4
登录后复制

5. 注意事项与总结

  • 无参构造函数:确保所有参与反序列化的类(包括基类和子类)都有一个公共的无参构造函数,Jackson需要它来实例化对象。
  • 子类声明完整性:@JsonSubTypes中应列出所有可能出现在JSON中的子类型。如果遗漏了某个子类,Jackson将无法推断其类型,可能会回退到defaultImpl或抛出异常。
  • defaultImpl的重要性:defaultImpl属性是可选的,但在Id.DEDUCTION策略下,它提供了强大的容错能力。它确保了即使JSON对象不完全匹配任何一个子类型(例如,它就是一个纯粹的基类对象),也能被正确反序列化为基类实例,避免了UnrecognizedPropertyException。
  • 其他多态策略:除了Id.DEDUCTION,Jackson还支持其他多态反序列化策略,如Id.NAME(通过JSON中的一个类型字段指定类型名)或Id.CLASS(通过JSON中的一个类型字段指定完整类名)。Id.DEDUCTION的优势在于它不需要JSON中包含额外的类型信息字段,而是通过属性结构进行推断。
  • 性能考量:Id.DEDUCTION策略在处理复杂类型层次结构时可能会比Id.NAME等策略略微消耗更多资源,因为它需要检查属性进行推断。但在大多数常见场景下,这种性能差异可以忽略不计。

通过正确配置@JsonTypeInfo和@JsonSubTypes注解,我们可以优雅地处理包含多类型对象的JSON数组,并将其反序列化为统一的基类列表,极大地增强了Jackson在处理复杂数据结构时的灵活性和健壮性。

以上就是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号