0

0

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

聖光之護

聖光之護

发布时间:2025-09-24 12:53:21

|

651人浏览过

|

来源于php中文网

原创

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

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

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

在实际的web服务交互中,我们经常会遇到json数组中包含多种类型对象的情况,这些对象可能共享一个共同的基类,但部分元素是其子类的实例,拥有基类所没有的额外属性。例如,一个车辆列表可能既包含普通轿车(car),也包含卡车(truck),其中truck是car的子类,并拥有如maxload、clearance等特有属性。

当尝试使用Jackson库将这样的JSON数组直接反序列化为List时,如果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>() {})进行反序列化,会遇到上述的UnrecognizedPropertyException。虽然将List替换为List可以避免异常,但这只会成功反序列化Truck实例,而普通Car实例则会被忽略或无法正确处理,导致数据丢失

解决方案:利用Jackson的@JsonTypeInfo和@JsonSubTypes

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

1. @JsonTypeInfo注解详解

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

Devin
Devin

世界上第一位AI软件工程师,可以独立完成各种开发任务。

下载
  • 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,Jackson将根据注解自动处理多态
            List cars = mapper.readValue(src, new TypeReference>() {});

            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和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数据中明确包含类型信息,提供了更精确的控制,但同时也增加了

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

418

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

535

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

311

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

77

2025.09.10

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

15

2025.11.27

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

15

2025.11.27

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

469

2024.01.03

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

13

2025.12.06

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

158

2026.01.28

热门下载

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

精品课程

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

共23课时 | 3万人学习

C# 教程
C# 教程

共94课时 | 7.8万人学习

Java 教程
Java 教程

共578课时 | 52.6万人学习

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

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