利用Jackson解决Json序列化和反序列化问题
作者:衍志 发布时间:2023-02-16 14:59:54
前言
在前后端分离的应用中,前端往往需要向后端发送命令请求,并将请求中的数据以Json格式传递。后端需要将Json格式的数据反序列化成Java对象,以便进行处理。但是,在实际应用中,我们会遇到一些挑战,比如多态类型处理的需求,以及需要将多个API压缩成一个API等。本文将介绍如何使用Jackson库解决这些问题。
Jackson库简介
Jackson库概述
Jackson是一个用于处理Json数据的Java库,由FasterXML开发和维护。它提供了一系列功能,包括Json序列化和反序列化、Json树模型、流式API等。
Jackson主要特性
支持Java对象与Json之间的转换
支持Json树模型
支持流式API
支持注解,灵活地控制序列化和反序列化过程
支持多态类型处理
JsonTypeInfo和JsonSubTypes的基本概念
JsonTypeInfo的作用与使用场景
在进行Json序列化和反序列化时,我们需要考虑Java对象的类型信息,以便正确地将Json数据转换成Java对象。JsonTypeInfo提供了一种机制,用于在序列化和反序列化时处理Java对象的类型信息。
JsonSubTypes的作用与使用场景
JsonSubTypes是JsonTypeInfo的一个子注解,用于指定Java类的子类。当我们需要序列化和反序列化一个包含多态类型的Java对象时,我们可以使用@JsonTypeInfo和@JsonSubTypes注解来指定子类的类型信息。
实际应用案例
代码示例:动物园
假设我们有一个动物园,里面有各种动物,包括狗、猫、鱼等。我们需要将动物园中的动物序列化成Json格式,并将其发送到前端。我们定义一个Animal类作为父类,然后定义三个子类:Dog、Cat和Fish。具体代码如下:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "dog"),
@JsonSubTypes.Type(value = Cat.class, name = "cat"),
@JsonSubTypes.Type(value = Fish.class, name = "fish")
})
public abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name);
this.breed = breed;
}
public String getBreed() {
return breed;
}
public void setBreed(String breed) {
this.breed = breed;
}
}
public class Cat extends Animal {
private String color;
public Cat(String name, String color) {
super(name);
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
public class Fish extends Animal {
private String species;
public Fish(String name, String species) {
super(name);
this.species = species;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
}
从父类到子类的序列化和反序列化
现在我们需要将动物园中的所有动物序列化成Json格式。我们可以使用ObjectMapper类的writeValueAsString()方法来实现。具体代码如下:
List<Animal> animals = new ArrayList<>();
animals.add(new Dog("旺财", "金毛"));
animals.add(new Cat("小花", "黑白"));
animals.add(new Fish("尼莫", "小丑鱼"));
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(animals);
反之,我们可以使用ObjectMapper类的readValue()方法将Json数据反序列化成Java对象。具体代码如下:
List<Animal> animals = objectMapper.readValue(json, new TypeReference<List<Animal>>() {});
自定义类型信息属性
我们可以通过@JsonTypeInfo注解的property属性来自定义类型信息的属性名。例如,我们将property属性的值设置为"animalType",则Json数据中的类型信息属性名也会变为"animalType"。具体代码如下:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "animalType")
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "dog"),
@JsonSubTypes.Type(value = Cat.class, name = "cat"),
@JsonSubTypes.Type(value = Fish.class, name = "fish")
})
public abstract class Animal {
...
}
好的,以下是使用markdown的翻译和例子:
JsonTypeInfo类
JsonTypeInfo
类是一个注解,用于配置在JSON序列化和反序列化中使用类型信息的详细信息,以保留有关对象实例的实际类的信息。这对于多态类型是必需的,并且还可能需要链接抽象声明类型和匹配的具体实现。
参数方法
JsonTypeInfo
类的参数方法如下:
use
:定义如何使用类型信息。有三个选项:Id.CLASS
,Id.NAME
和Id.MINIMAL_CLASS
。默认值为Id.CLASS
。
include
:定义类型信息的包含方式。有四个选项:As.PROPERTY
,As.WRAPPER_OBJECT
,As.EXISTING_PROPERTY
和As.EXTERNAL_PROPERTY
。默认值为As.PROPERTY
。
property
:定义包含类型信息的属性的名称。默认值为"$type"
。
例子
以下是使用JsonTypeInfo
类的一些例子:
// 将Java类名("com.myempl.ImplClass")包含在JSON属性"class"中
@JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY, property = "class")
// 将逻辑类型名称(在实现类中定义)作为包装器包含;2个注释
@JsonTypeInfo(use = Id.NAME, include = As.WRAPPER_OBJECT)
@JsonSubTypes({
@JsonSubTypes.Type(value = com.myemp.Impl1.class, name = "impl1"),
@JsonSubTypes.Type(value = com.myempl.Impl2.class, name = "impl2")
})
在第一个例子中,use
参数设置为Id.CLASS
,这意味着使用Java类名作为类型信息。include
参数设置为As.PROPERTY
,这意味着类型信息将包含在JSON属性"class"
中。property
参数设置为"class"
,这是包含类型信息的属性的名称。
在第二个例子中,use
参数设置为Id.NAME
,这意味着使用逻辑类型名称作为类型信息。include
参数设置为As.WRAPPER_OBJECT
,这意味着类型信息将作为包装器包含。@JsonSubTypes
注解用于指定实现类,并将它们的逻辑类型名称与之关联。
JsonSubTypes Annotation
@JsonSubTypes
是一个用于与 @JsonTypeInfo
一起使用的注解,用于指定可序列化的多态类型的子类型,并将逻辑名称与 JSON 内容中使用的名称关联起来。
注意,仅注释属性或基本类型不会启用多态类型处理:此外,需要使用 @JsonTypeInfo
或等效注释(例如启用所谓的“默认键入”)注释,并且仅在这种情况下才使用子类型信息。
可用的元素
@JsonSubTypes
注解具有以下元素:
元素 | 类型 | 描述 |
---|---|---|
value | Type[] | 注释类型的子类型(注释类或关联到注释方法的属性值类型)。这些将被递归检查,以便可以仅通过包含直接子类型来定义类型 |
@Type | 注解 | 子类型的定义和可选名称。如果未定义名称(空字符串将被忽略),类型的类将被检查是否有 @JsonTypeName 注解;如果也缺少或为空,则将通过类型 id 机制构造默认名称。默认名称通常基于类名 |
Type
@Type
是 @JsonSubTypes
中的一个注解,用于声明子类型。
@Type
注解具有以下元素:
元素 | 类型 | 描述 |
---|---|---|
value | Class<?> | 子类型的类 |
name | String | 类型标识符所使用的逻辑类型名称,如果定义了,则为空字符串。除非 names 定义为非空。默认情况下使用,如果类没有 @JsonTypeName 注解的话,会使用默认名称构造类型 id 机制 |
names | String[] | (可选)用于类的类型标识符的逻辑类型名称:如果应将多个类型名称与同一类型关联,则使用 |
使用示例
下面是一个示例,展示了如何使用 @JsonSubTypes
注解:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "dog"),
@JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public abstract class Animal {
// ...
}
在上面的示例中,Animal
是一个抽象类,它被注释为可以有子类型。@JsonTypeInfo
注解指定了要使用名称作为类型标识符,并将类型标识符作为 JSON 属性的一部分包含在 JSON 中。@JsonSubTypes
注解指定了 Dog
和 Cat
作为 Animal
的子类型,分别使用名称 "dog" 和 "cat" 作为它们的类型标识符。
高级用法
解决循环引用问题
在进行Json序列化时,如果Java对象之间存在循环引用,就会导致序列化失败。为了解决这个问题,我们可以使用@JsonIdentityInfo注解来指定对象的标识信息。具体代码如下:
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Employee {
private int id;
private String name;
private Employee manager;
public Employee(int id, String name, Employee manager) {
this.id = id;
this.name = name;
this.manager = manager;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Employee getManager() {
return manager;
}
public void setManager(Employee manager) {
this.manager = manager;
}
}
自定义类型解析器
有时候,我们需要对一些特殊类型的数据进行特殊处理,比如将日期字符串转换成Date对象。我们可以使用JsonDeserializer和JsonSerializer来自定义类型的解析和序列化过程。具体代码如下:
public class DateDeserializer extends JsonDeserializer<Date> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String dateStr = jsonParser.getText();
try {
return dateFormat.parse(dateStr);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
public class DateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(dateFormat.format(date));
}
}
@JsonDeserialize(using = DateDeserializer.class)
@JsonSerialize(using = DateSerializer.class)
public class Event {
private String name;
private Date date;
public Event(String name, Date date) {
this.name = name;
this.date = date;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
使用@JsonTypeName自定义类型名称
有时候,我们希望在Json数据中使用自定义的类型名称,而不是Java类的名称。可以使用@JsonTypeName注解来实现。具体代码如下:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "dog"),
@JsonSubTypes.Type(value = Cat.class, name = "cat"),
@JsonSubTypes.Type(value = Fish.class, name = "fish")
})
public abstract class Animal {
...
}
@JsonTypeName("dog")
public class Dog extends Animal {
...
}
性能优化
避免重复解析
在进行Json序列化和反序列化时,我们可能会重复解析同一个Json数据。为了提高性能,我们可以使用ObjectMapper类的readTree()方法将Json数据解析成JsonNode对象,并在需要的时候对其进行操作。具体代码如下:
JsonNode root = objectMapper.readTree(json);
JsonNode nameNode = root.path("name");
String name = nameNode.asText();
使用缓存机制
在进行Json序列化和反序列化时,我们可以使用缓存机制来提高性能。Jackson库提供了一个ObjectReader类,它可以缓存Json数据的解析结果,并在下次解析相同的Json数据时直接返回缓存结果,从而避免重复解析。具体代码如下:
ObjectReader reader = objectMapper.readerFor(new TypeReference<List<Animal>>() {});
List<Animal> animals = reader.readValue(json);
注意事项与最佳实践
处理不同版本的数据结构
在进行Json序列化和反序列化时,我们需要考虑到数据结构的版本变化。如果数据结构发生变化,我们需要进行相应的兼容处理,以确保新版本的程序能够正确地处理旧版本的数据。为了处理不同版本的数据结构,我们可以使用@JsonCreator和@JsonValue注解来自定义对象的构造函数和toString()方法。
使用@JsonSubTypes的局限性
使用@JsonSubTypes注解时,需要注意它的局限性。它只能用于序列化和反序列化Java对象,无法用于处理基本类型和数组类型。
安全性考虑
在进行Json序列化和反序列化时,我们需要考虑到安全性问题。由于Json数据是开放的,存在被恶意篡改和注入的风险。为了防止这种风险,我们可以使用@JsonInclude注解来控制序列化时包含的属性,以及使用@JsonCreator和@JsonValue注解来限制反序列化的输入。此外,我们还可以使用JsonNode对象来手动解析Json数据,以避免使用反序列化机制。
来源:https://juejin.cn/post/7237082256479698999


猜你喜欢
- 首先看一下泛型的基本语法访问修饰符 返回类型 泛型方法名 <T>(T 参数)1):无法在泛型方法内部给任何 T 类型创建实例的对
- /// 除去所有在HTML元素中标记 public static string&nbs
- 一、项目运行环境配置:Jdk1.8 + Tomcat8.5 + Mysql + HBuilderX(Webstorm也行)+ Eclispe
- 对某个类型中的方法进行拦截,然后加入固定的业务逻辑,这是AOP面向切面编程可以做的事,在springboot里实现aop的方法也有很多, s
- 对象嵌套关联查询一对多List集合查询mybatis嵌套关联查询如下由于我的是一对集合查询,所以我有两个类。@Data@TableName(
- mybatis自动生成实体类、mapper文件、mapper.xml文件若采用mybatis框架,数据库新建表,手动编写的话,需要编写大量的
- 导航是指支持用户导航、进入和退出应用中不同内容片段的交互。Android Jetpack 的导航组件可帮助您实现导航,无论是简单的按钮点击,
- 前言本篇文章讲的是Kotlin 自定义view之实现标尺控件Ruler,以选择身高、体重等。开发中,当我们需要获取用户的身高和体重等信息时,
- C# 输出参数out什么是输出参数方法声明时,使用out修饰符声明的形参,成为输出参数。输出参数的特点1、输出参数不创建新的储存位置。2、输
- Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,
- 我们在编程的时候,有时会使用多线程来解决问题,比如你的程序需要在后台处理一大堆数据,但还要使用户界面处于可操作状态;或者你的程序需要访问一些
- 使用通配符增强泛型1.题目泛型是JAVA重要的特性,使用泛型编程,可以使代码复用率提高。实现:在泛型方法中使用通配符2.解题思路创建一个类:
- 前言首先,我们要讲的是JVM的垃圾回收机制,我默认准备阅读本篇的人都知道以下两点:JVM是做什么的Java堆是什么因为我们即将要讲的就是发生
- notification是一种让你的应用程序在没有开启情况下或在后台运行警示用户。它是看不见的程序组件(Broadcast Receiver
- 前言现在大部分App底部都有一个菜单,实现这个功能也有好多办法:- TabHost+Fragment - RadioGroup+Fragme
- 1. 布局: GridBagLayout官方JavaDocsApi: java.awt.GridBagLayoutGridBagLayout
- 这篇文章主要介绍了Spring Boot Logback配置日志过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考
- 项目要求基于Broadcast,BroadcastReceiver等与广播相关的知识实现简单的音乐播放功能,包括音乐的播放、暂停、切换、进度
- 在spring运行时,动态的添加bean,dapeng框架在解析xml的字段时,使用到了动态注册,注册了一个实现了FactoryBean类!
- 简介JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation。在任何时候,当你要处理一个应用程序的业务逻