0

0

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

聖光之護

聖光之護

发布时间:2025-09-24 17:19:01

|

787人浏览过

|

来源于php中文网

原创

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

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

1. 问题背景与挑战

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

示例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时,会遇到com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException异常。这是因为Jackson在尝试将包含maxLoad和clearance属性的JSON对象映射到Car类时,发现Car类中没有这些属性,从而抛出异常。虽然将目标类型改为List可以避免异常,但这会导致所有对象都被尝试反序列化为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:

Postme
Postme

Postme是一款强大的AI写作工具,可以帮助您快速生成高质量、原创的外贸营销文案,助您征服全球市场。

下载
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
            List cars = mapper.readValue(jsonInput, new TypeReference>() {});

            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{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在处理复杂数据结构时的灵活性和健壮性。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

825

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

724

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

728

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

395

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

398

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

445

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

429

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16881

2023.08.03

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

61

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.1万人学习

C# 教程
C# 教程

共94课时 | 5.7万人学习

Java 教程
Java 教程

共578课时 | 40.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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