0

0

Java Jackson 库中多态类型反序列化:处理父类属性为子类实例

心靈之曲

心靈之曲

发布时间:2025-12-01 20:09:13

|

515人浏览过

|

来源于php中文网

原创

java jackson 库中多态类型反序列化:处理父类属性为子类实例

本教程深入探讨了在 Java 中使用 Jackson 库进行多态反序列化时,如何将 JSON 中的父类属性正确地反序列化为子类实例。文章重点介绍了 `@JsonTypeInfo` 和 `@JsonSubTypes` 注解的用法,并解决了在 JSON 数据中缺少类型标识符时遇到的常见问题,强调了 JSON 结构与注解配置保持一致的重要性,提供了修改 JSON 结构以实现预期反序列化的解决方案。

在 Java 开发中,处理多态类型是常见的需求,尤其是在对象序列化和反序列化时。当一个类(如 Family)包含一个父类类型的属性(如 Parent),但实际运行时该属性可能是一个具体的子类实例(如 ChildA 或 ChildB)时,Jackson 库在反序列化过程中需要明确的指导才能正确地将 JSON 数据映射到对应的子类对象。

理解多态反序列化挑战

考虑以下类结构:

// Family 类包含一个 Parent 类型的属性
class Family {
    String address;
    Parent parent; // 实际运行时可能是 ChildA 或 ChildB
    // ... 其他属性和方法
}

// Parent 是基类
class Parent {
    String parentAttribute;
    // ... 其他属性和方法
}

// ChildA 继承自 Parent
class ChildA extends Parent {
    String attributeA;
    // ... 其他属性和方法
}

// ChildB 继承自 Parent
class ChildB extends Parent {
    String attributeB;
    // ... 其他属性和方法
}

假设我们有一个 JSON 字符串,它代表一个 Family 对象,其中 parent 字段实际上是一个 ChildA 的实例,例如:

立即学习Java免费学习笔记(深入)”;

{
    "address": "123 Main St",
    "parent": {
        "parentAttribute": "Mom",
        "attributeA": "Child A Type"
    }
}

当我们尝试将这个 JSON 反序列化为 Family 对象时,如果没有额外的配置,Jackson 默认会将 parent 字段反序列化为 Parent 类型,而 attributeA 这样的子类特有属性则会被忽略,因为 Parent 类中没有定义这些属性。为了让 Jackson 能够识别并创建正确的子类实例,我们需要使用 Jackson 提供的多态类型处理机制。

使用 Jackson 注解实现多态反序列化

Jackson 提供了 @JsonTypeInfo 和 @JsonSubTypes 注解来处理多态类型的序列化和反序列化。

  1. @JsonTypeInfo: 这个注解应用于父类(或接口),用于指示 Jackson 如何在 JSON 中包含类型信息。

    • use = JsonTypeInfo.Id.NAME: 指定使用一个逻辑名称作为类型标识符。
    • include = JsonTypeInfo.As.PROPERTY: 指定类型信息作为 JSON 对象的一个独立属性包含。
    • property = "type": 定义这个类型标识符属性的名称,例如 type。
  2. @JsonSubTypes: 这个注解也应用于父类(或接口),用于将逻辑名称映射到具体的子类。

    • @JsonSubTypes.Type(value = ChildA.class, name = "A"): 将逻辑名称 "A" 映射到 ChildA 类。
    • @JsonSubTypes.Type(value = ChildB.class, name = "B"): 将逻辑名称 "B" 映射到 ChildB 类。

根据上述说明,我们修改 Parent 类:

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

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = ChildA.class, name = "A"),
        @JsonSubTypes.Type(value = ChildB.class, name = "B")
})
class Parent {
    String parentAttribute;
    // ... 构造函数、getter/setter
}

class ChildA extends Parent {
    String attributeA;
    // ... 构造函数、getter/setter
}

class ChildB extends Parent {
    String attributeB;
    // ... 构造函数、getter/setter
}

class Family {
    String address;
    Parent parent;
    // ... 构造函数、getter/setter
}

解决“缺少类型标识符”问题

在上述注解配置完成后,当我们尝试反序列化最初的 JSON 字符串时,可能会遇到以下错误:

com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class Family ]: missing type id property 'type' (for POJO property 'parent')

Humata
Humata

Humata是用于文件的ChatGPT。对你的数据提出问题,并获得由AI提供的即时答案。

下载

这个错误信息明确指出问题所在:Jackson 在尝试反序列化 parent 属性时,期望在 JSON 数据中找到一个名为 type 的属性来确定具体的子类类型,但当前的 JSON 字符串中缺少这个 type 属性。

原始的 JSON 结构:

{
    "address": "123 Main St",
    "parent": {
        "parentAttribute": "Mom",
        "attributeA": "Child A Type"
    }
}

它并没有包含任何 type 字段,因此 Jackson 无法根据 @JsonTypeInfo 的配置识别出是 ChildA 还是 ChildB。

解决方案:修改 JSON 结构以包含类型标识符

最直接且推荐的解决方案是修改 JSON 字符串,使其包含 Jackson 所期望的类型标识符属性。根据 @JsonTypeInfo(..., property = "type") 的配置,我们需要在 parent 对象内部添加一个 type 属性,其值对应 @JsonSubTypes 中定义的 name。

例如,如果 parent 属性实际上是 ChildA 类型,那么 JSON 应该修改为:

{
    "address": "123 Main St",
    "parent": {
        "type": "A", // 添加类型标识符
        "parentAttribute": "Mom",
        "attributeA": "Child A Type"
    }
}

如果 parent 属性是 ChildB 类型,则 JSON 应该是:

{
    "address": "123 Main St",
    "parent": {
        "type": "B", // 添加类型标识符
        "parentAttribute": "Dad",
        "attributeB": "Child B Type"
    }
}

有了这个 type 字段,Jackson 在反序列化时就能正确地识别出 parent 属性应该被反序列化为 ChildA 或 ChildB 的实例。

示例代码:完整反序列化流程

以下是一个完整的 Java 示例,演示如何使用修改后的 JSON 字符串进行多态反序列化:

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

// Family 类
class Family {
    public String address;
    public Parent parent;

    public Family() {} // Jackson 需要无参构造函数
    public Family(String address, Parent parent) {
        this.address = address;
        this.parent = parent;
    }

    @Override
    public String toString() {
        return "Family{" +
               "address='" + address + '\'' +
               ", parent=" + parent +
               '}';
    }
}

// Parent 基类,添加多态注解
@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = ChildA.class, name = "A"),
        @JsonSubTypes.Type(value = ChildB.class, name = "B")
})
abstract class Parent { // 建议将基类声明为抽象类
    public String parentAttribute;

    public Parent() {}
    public Parent(String parentAttribute) {
        this.parentAttribute = parentAttribute;
    }

    @Override
    public String toString() {
        return "Parent{" +
               "parentAttribute='" + parentAttribute + '\'' +
               '}';
    }
}

// ChildA 子类
class ChildA extends Parent {
    public String attributeA;

    public ChildA() {}
    public ChildA(String parentAttribute, String attributeA) {
        super(parentAttribute);
        this.attributeA = attributeA;
    }

    @Override
    public String toString() {
        return "ChildA{" +
               "parentAttribute='" + parentAttribute + '\'' +
               ", attributeA='" + attributeA + '\'' +
               '}';
    }
}

// ChildB 子类
class ChildB extends Parent {
    public String attributeB;

    public ChildB() {}
    public ChildB(String parentAttribute, String attributeB) {
        super(parentAttribute);
        this.attributeB = attributeB;
    }

    @Override
    public String toString() {
        return "ChildB{" +
               "parentAttribute='" + parentAttribute + '\'' +
               ", attributeB='" + attributeB + '\'' +
               '}';
    }
}

public class PolymorphicDeserializationDemo {
    public static void main(String[] args) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();

        // 示例 JSON 字符串,包含 ChildA 类型信息
        String jsonStringA = "{" +
                             "\"address\": \"123 Main St\"," +
                             "\"parent\": {" +
                             "\"type\": \"A\"," + // 关键的类型标识符
                             "\"parentAttribute\": \"Mom\"," +
                             "\"attributeA\": \"Child A Type\"" +
                             "}" +
                             "}";

        // 示例 JSON 字符串,包含 ChildB 类型信息
        String jsonStringB = "{" +
                             "\"address\": \"456 Oak Ave\"," +
                             "\"parent\": {" +
                             "\"type\": \"B\"," + // 关键的类型标识符
                             "\"parentAttribute\": \"Dad\"," +
                             "\"attributeB\": \"Child B Type\"" +
                             "}" +
                             "}";

        // 反序列化 ChildA
        Family familyA = objectMapper.readValue(jsonStringA, Family.class);
        System.out.println("Deserialized Family A: " + familyA);
        System.out.println("Parent type in Family A: " + familyA.parent.getClass().getName());
        if (familyA.parent instanceof ChildA) {
            ChildA childA = (ChildA) familyA.parent;
            System.out.println("ChildA specific attribute: " + childA.attributeA);
        }
        System.out.println("------------------------------------");

        // 反序列化 ChildB
        Family familyB = objectMapper.readValue(jsonStringB, Family.class);
        System.out.println("Deserialized Family B: " + familyB);
        System.out.println("Parent type in Family B: " + familyB.parent.getClass().getName());
        if (familyB.parent instanceof ChildB) {
            ChildB childB = (ChildB) familyB.parent;
            System.out.println("ChildB specific attribute: " + childB.attributeB);
        }
    }
}

输出示例:

Deserialized Family A: Family{address='123 Main St', parent=ChildA{parentAttribute='Mom', attributeA='Child A Type'}}
Parent type in Family A: ChildA
ChildA specific attribute: Child A Type
------------------------------------
Deserialized Family B: Family{address='456 Oak Ave', parent=ChildB{parentAttribute='Dad', attributeB='Child B Type'}}
Parent type in Family B: ChildB
ChildB specific attribute: Child B Type

从输出可以看出,Jackson 成功地将 parent 属性反序列化成了正确的子类 (ChildA 或 ChildB) 实例。

注意事项与总结

  1. JSON 结构与注解匹配是关键:Jackson 的多态反序列化机制依赖于 JSON 数据中明确的类型标识符。@JsonTypeInfo 配置的 property 名称(例如 type)必须与 JSON 中实际的字段名一致。
  2. 控制 JSON 源:如果能够控制 JSON 数据的生成方,那么在 JSON 中包含类型标识符是最简单、最健壮的解决方案。
  3. 自定义反序列化器(高级场景):如果无法修改 JSON 结构(例如,接收的是第三方数据源),并且 JSON 中没有明确的类型标识符,但可以通过其他属性的组合来推断类型,那么可能需要实现自定义的 JsonDeserializer。然而,这种方法通常更复杂,且容易出错,因为它需要手动编写类型推断逻辑。
  4. 基类设计:将多态基类(如 Parent)设计为抽象类是一个好习惯,因为它通常不应该被直接实例化,而只作为子类的通用接口。

通过正确配置 Jackson 的多态注解并确保 JSON 数据包含必要的类型标识符,可以有效地解决 Java 中父类属性反序列化为子类实例的问题,从而实现灵活且强大的数据处理能力。

相关专题

更多
java
java

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

837

2023.06.15

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

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

741

2023.07.05

java自学难吗
java自学难吗

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

737

2023.07.31

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

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

397

2023.08.01

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

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

399

2023.08.02

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

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

446

2023.08.02

java有什么用
java有什么用

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

430

2023.08.02

java在线网站
java在线网站

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

16926

2023.08.03

PHP WebSocket 实时通信开发
PHP WebSocket 实时通信开发

本专题系统讲解 PHP 在实时通信与长连接场景中的应用实践,涵盖 WebSocket 协议原理、服务端连接管理、消息推送机制、心跳检测、断线重连以及与前端的实时交互实现。通过聊天系统、实时通知等案例,帮助开发者掌握 使用 PHP 构建实时通信与推送服务的完整开发流程,适用于即时消息与高互动性应用场景。

11

2026.01.19

热门下载

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

精品课程

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

共23课时 | 2.7万人学习

C# 教程
C# 教程

共94课时 | 7万人学习

Java 教程
Java 教程

共578课时 | 47.8万人学习

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

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