搜索
首页 > Java > java教程 > 正文

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

碧海醫心
发布: 2025-09-24 13:08:01
原创
358人浏览过

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

本文旨在解决Jackson在反序列化包含基类和子类混合对象的JSON数组时遇到的UnrecognizedPropertyException问题。通过引入@JsonTypeInfo和@JsonSubTypes注解,利用Id.DEDUCTION策略和defaultImpl配置,Jackson能够智能地识别JSON对象类型,并将其正确地反序列化为List<BaseClass>,其中包含基类和其子类的实例,从而实现灵活的多态性数据处理。

1. 问题背景与挑战

java开发中,我们经常需要处理来自外部服务或文件中的json数据。当json数组中包含不同类型但有继承关系的java对象时,例如一个包含car和truck(truck继承自car)实例的数组,并尝试将其反序列化为list<car>时,jackson的默认行为可能会导致问题。

考虑以下Java类结构:

public class Car {
    private String make;
    private String model;
    private short  year;
    private String bodyStyle;
    private String engineType;
    private int    horsepower;
    // 省略setter和getter
    // 为了演示,添加一个toString方法
    @Override
    public String toString() {
        return "Car{" +
               "make='" + make + '\'' +
               ", model='" + model + '\'' +
               ", year=" + year +
               ", bodyStyle='" + bodyStyle + '\'' +
               ", engineType='" + engineType + '\'' +
               ", horsepower=" + horsepower +
               '}';
    }
    // 构造函数
    public Car() {}
    public Car(String make, String model, short year, String bodyStyle, String engineType, int horsepower) {
        this.make = make;
        this.model = model;
        this.year = year;
        this.bodyStyle = bodyStyle;
        this.engineType = engineType;
        this.horsepower = 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; }
}

public class Truck extends Car {
    private double  maxLoad;
    private double  clearance;
    // 省略setter和getter
    // 为了演示,添加一个toString方法
    @Override
    public String toString() {
        return "Truck{" +
               "make='" + getMake() + '\'' +
               ", model='" + getModel() + '\'' +
               ", year=" + getYear() +
               ", bodyStyle='" + getBodyStyle() + '\'' +
               ", engineType='" + getEngineType() + '\'' +
               ", horsepower=" + getHorsepower() +
               ", maxLoad=" + maxLoad +
               ", clearance=" + clearance +
               '}';
    }
    // 构造函数
    public Truck() {}
    public Truck(String make, String model, short year, String bodyStyle, String engineType, int horsepower, double maxLoad, double clearance) {
        super(make, model, year, bodyStyle, engineType, horsepower);
        this.maxLoad = maxLoad;
        this.clearance = 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; }
}
登录后复制

以及对应的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
  },
  {
    "make": "Ford",
    "model": "T150",
    "year": 2017,
    "horsepower": 450,
    "bodyStyle": "pickup",
    "maxLoad": 2320,
    "clearance": 8.4
  }
]
登录后复制

当我们尝试使用ObjectMapper将上述JSON反序列化为List<Car>时,会遇到com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException异常,因为Car类不包含maxLoad或clearance等Truck特有的属性。Jackson在尝试将一个实际是Truck的JSON对象映射到Car类型时,无法识别这些额外属性。

2. 解决方案:Jackson多态反序列化注解

Jackson提供了强大的多态性反序列化机制,允许根据JSON数据的内容动态地确定要创建的Java对象类型。这主要通过@JsonTypeInfo和@JsonSubTypes这两个注解来实现。

2.1 @JsonTypeInfo注解

@JsonTypeInfo用于指定多态类型信息的处理方式。在本场景中,我们将使用Id.DEDUCTION策略。

  • use = JsonTypeInfo.Id.DEDUCTION: 告诉Jackson通过检查JSON对象的字段来推断实际类型。它会尝试将JSON对象与所有已知的子类型进行匹配。
  • defaultImpl = Car.class: 当DEDUCTION策略无法将JSON对象匹配到任何一个子类型时,Jackson将使用defaultImpl指定的默认类型进行反序列化。这对于那些只包含基类属性的JSON对象非常有用。

2.2 @JsonSubTypes注解

@JsonSubTypes用于列出基类的所有已知子类型。Jackson会根据这些子类型的信息,结合@JsonTypeInfo的策略来执行类型推断。

  • @JsonSubTypes.Type(value = Truck.class): 声明Truck是Car的一个子类型。如果有多个子类型,可以添加多个@JsonSubTypes.Type。

2.3 应用注解到基类

为了解决上述问题,我们需要在基类Car上添加这两个注解:

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

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

序列猴子开放平台0
查看详情 序列猴子开放平台
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)
})
public class Car {
    private String make;
    private String model;
    private short  year;
    private String bodyStyle;
    private String engineType;
    private int    horsepower;
    // 省略setter和getter以及toString()和构造函数,同上
    // ...
}

public class Truck extends Car {
    private double  maxLoad;
    private double  clearance;
    // 省略setter和getter以及toString()和构造函数,同上
    // ...
}
登录后复制

3. 代码示例与验证

现在,我们可以使用更新后的Car和Truck类进行反序列化。

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 PolymorphicDeserializationDemo {
    private static final ObjectMapper mapper = new ObjectMapper();

    public static void main(String[] args) {
        // 假设cars.json包含前面提到的JSON数据
        InputStream src = PolymorphicDeserializationDemo.class.getClassLoader().getResourceAsStream("cars.json");
        if (src == null) {
            System.err.println("Error: cars.json not found in classpath.");
            return;
        }

        try {
            // 反序列化为List<Car>
            List<Car> vehicles = mapper.readValue(src, new TypeReference<List<Car>>() {});

            System.out.println("Deserialized Vehicles:");
            for (Car vehicle : vehicles) {
                System.out.println("Type: " + vehicle.getClass().getSimpleName() + ", Details: " + vehicle);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (src != null) {
                    src.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
登录后复制

运行上述代码,输出将不再是UnrecognizedPropertyException,而是成功地打印出每个对象的实际类型和内容:

Deserialized Vehicles:
Type: Car, Details: Car{make='Ford', model='Focus', year=2018, bodyStyle='hatchback', engineType='T4', horsepower=225}
Type: Truck, Details: Truck{make='Toyota', model='Tacoma', year=2021, bodyStyle='pickup', engineType='V6', horsepower=278, maxLoad=1050.0, clearance=9.4}
Type: Truck, Details: Truck{make='Ford', model='T150', year=2017, bodyStyle='pickup', engineType='null', horsepower=450, maxLoad=2320.0, clearance=8.4}
登录后复制

可以看到,Focus被正确地反序列化为Car类型,而Tacoma和T150则被正确地反序列化为Truck类型,且所有对象都存储在List<Car>中。

4. 工作原理详解

当Jackson遇到一个JSON对象时,它会按照以下逻辑进行处理:

  1. 类型推断(Id.DEDUCTION): Jackson会遍历@JsonSubTypes中定义的每一个子类型(例如Truck)。
    • 对于每个子类型,它会尝试将JSON对象的属性与子类型的属性进行匹配。如果JSON对象包含某个子类型特有的属性(例如Truck特有的maxLoad或clearance),Jackson就会推断该JSON对象应该被反序列化为该子类型。
    • 例如,当它看到"maxLoad": 1050时,它会发现Truck类有这个属性,而Car没有,因此它推断这是一个Truck对象。
  2. 默认类型回退(defaultImpl): 如果JSON对象的所有属性都无法与任何一个已声明的子类型特有属性匹配,Jackson就会回退到@JsonTypeInfo中指定的defaultImpl类型。
    • 例如,Focus的JSON对象只包含Car的属性,不包含Truck的特有属性,因此Jackson会将其反序列化为Car类型。

这种机制使得Jackson能够智能地处理JSON数据中的多态性,无需在JSON中显式地包含类型标识符(如@class字段),从而保持JSON的简洁性。

5. 注意事项与最佳实践

  • 属性命名冲突: 确保子类特有的属性名称在基类中不存在,否则可能导致推断不准确。
  • 子类型注册: 务必在@JsonSubTypes中注册所有可能的子类型,否则未注册的子类型将无法被正确推断,并可能回退到defaultImpl。
  • 性能考量: Id.DEDUCTION策略在处理大量子类型或复杂对象结构时,可能会比显式类型标识符(如Id.CLASS或Id.NAME)略微影响性能,因为它需要进行属性匹配。对于性能敏感的场景,可以考虑在JSON中添加一个类型字段来明确指定类型。
  • 默认实现的重要性: defaultImpl提供了强大的回退机制。如果JSON数据可能不完全匹配任何子类型,或者你希望某些通用对象始终被反序列化为基类,那么defaultImpl是必不可少的。
  • Jackson版本: 确保使用的Jackson版本支持Id.DEDUCTION(通常在较新的版本中可用,如2.10+)。

6. 总结

通过巧妙地使用Jackson的@JsonTypeInfo和@JsonSubTypes注解,我们可以优雅地解决JSON数组中包含混合继承关系对象时的反序列化问题。Id.DEDUCTION策略结合defaultImpl提供了一种无需修改JSON结构即可实现多态性反序列化的强大方法,使得代码更加健壮和灵活,能够适应不同复杂度的JSON数据场景。理解并掌握这些注解的使用,是高效处理复杂JSON数据结构的关键技能。

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

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

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

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

Copyright 2014-2025 //m.sbmmt.com/ All Rights Reserved | php.cn | 湘ICP备2023035733号