Appearance
📌 前置学习:JDK序列化与JSON序列化 Java安全之反序列化漏洞
Jackson是一个强大而高效的Java库,处理Java对象及其JSON表示的序列化和反序列化。它是这项任务中使用最广泛的库之一,并在许多其他框架中作为默认的Json引擎使用
Jackson操作
ObjectMapper类
ObjectMapper类是Jackson库中最重要的类,它提供了序列化和反序列化Java对象与JSON之间的转换,ObjectMapper类的实例是线程安全的,可以在多线程环境中共享,Jackson库中用于读写JSON的主要类是ObjectMapper,可以序列化和反序列化两种类型的对象
- 普通的旧Java对象(POJO)
- 通用的JSON树模型
基础类型JSON序列化
对象序列化
将Java对象转换为JSON字符串最基本的方法就是使用ObjectMapper类的writeValueAsString方法,这个方法接收一个Java对象作为参数,返回一个JSON字符串。
java
ObjectMapper mapper = new ObjectMapper();
User user = new User("Tom", 20);
String json = mapper.writeValueAsString(user);上面的代码中,我们创建了一个User对象,并使用ObjectMapper类将其序列化为JSON字符串。User类的定义如下
java
public class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}生成的JSON字符串如下:
java
{"name":"Tom","age":20}集合序列化
除了序列化单个Java对象,Jackson库还支持序列化Java集合,包括List、Set和Map等。可以使用ObjectMapper类的writeValueAsString方法将Java集合序列化为JSON字符串
java
ObjectMapper mapper = new ObjectMapper();
List<User> users = new ArrayList<>();
users.add(new User("Tom", 20));
users.add(new User("Jerry", 22));
String json = mapper.writeValueAsString(users);上面的代码中,我们创建了一个List集合,并将两个User对象添加到集合中,然后使用ObjectMapper类将集合序列化为JSON字符串。
生成的JSON字符串如下:
java
[{"name":"Tom","age":20},{"name":"Jerry","age":22}]序列化枚举类型
在Java中,枚举类型是一种常见的数据类型,它通常用于表示一组有限的值。使用Jackson库将枚举类型序列化为JSON字符串也是常见的操作。下面是一个简单的枚举类型的定义:
java
public enum Gender {
MALE, FEMALE
}要将枚举类型序列化为JSON字符串,我们只需要在类上添加@JsonFormat注解,并指定序列化的格式。例如,以下代码将会使用大写字母序列化枚举类型
java
public class User {
private String name;
private int age;
private Gender gender;
// getters and setters
}
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
User user = new User();
user.setName("Tom");
user.setAge(20);
user.setGender(Gender.MALE);
String json = mapper.writeValueAsString(user);
System.out.println(json);输出结果如下:
java
{
"name" : "Tom",
"age" : 20,
"gender" : "MALE"
}在上面的代码中,我们先定义了一个User类,其中包含一个枚举类型的字段gender。然后,我们使用ObjectMapper类将User对象序列化为JSON字符串,并将结果打印出来。可以看到,枚举类型的值已经被序列化为字符串类型了
基础类型JSON反序列化
反序列化和序列化没什么区别,就是单纯逆转过程,不过其中有个需要注意的点
由于Java的泛型擦除机制,不能直接将List<User>传给readValue方法,需要使用TypeReference类来指定集合类型
换句话说,当我们使用泛型时,Jackson序列化可能会丢失泛型信息,导致反序列化时出现问题
问题描述
java
public class Response<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}当我们使用泛型时,Jackson序列化可能会丢失泛型信息,在上面的代码中,我们定义了一个Response类,它使用泛型来表示响应数据。现在,我们将使用Jackson将Response对象序列化为JSON字符串
java
ObjectMapper mapper = new ObjectMapper();
Response<String> response = new Response<>();
response.setData("Hello, world!");
String json = mapper.writeValueAsString(response);在上面的代码中,我们使用ObjectMapper将Response对象序列化为JSON字符串。然而,当我们尝试将JSON字符串反序列化为Response对象时,我们会遇到问题:
java
Response<String> response = mapper.readValue(json, Response.class);在上面的代码中,我们尝试将JSON字符串反序列化为Response对象。然而,由于Jackson序列化丢失了泛型信息,我们无法正确地反序列化Response对象。
解决方案
@JsonTypeInfo()
- @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "type", visible = true, defaultImpl = Action.class, include = JsonTypeInfo.As.EXISTING_PROPERTY)
- 用于配置JSON 序列化和反序列化过程中使用哪种类型信息,该注解是实现多态类型的注解
- include:
- JsonTypeInfo.As.PROPERTY:序列化时,类型标识字段作为json的一个字段。此时无论json中是否已经存在该字段。因此
visible = true和include = JsonTypeInfo.As.EXISTING_PROPERTY搭配时,会导致序列化后json有两个重复的字段 - JsonTypeInfo.As.WRAPPER_OBJECT:序列化时,在json中类型标识字段会使用大括号包裹起来
- JsonTypeInfo.As.WRAPPER_ARRAY:序列化时,在json中类型标识字段会使用中括号包裹起来
- JsonTypeInfo.As.EXTERNAL_PROPERTY:序列化时,在json中类型标识字段和序列化的类同级
- JsonTypeInfo.As.EXISTING_PROPERTY:序列化时,如果json中已经有同名字段,那么类型标识字段不序列化
- JsonTypeInfo.As.PROPERTY:序列化时,类型标识字段作为json的一个字段。此时无论json中是否已经存在该字段。因此
- defaultImpl:指定默认类,用于Jackson不能反序列化为任意一个子类时使用。可以填写
Void,将反序列化为null。默认值为JsonTypeInfo.class,抛出com.fasterxml.jackson.databind.exc.InvalidTypeIdException异常。 - visible:当为
true时,property中用到的字段将参与序列化和反序列化中;为false时不参与序列化和反序列化 - property : 指定哪个字段用于标识类型
- use:用于指定用哪种类型的元信息来序列化和反序列化。有下面几种选择
- JsonTypeInfo.Id.NONE: JSON字符串中不包含显式的元信息用于识别类型。开发人员使用其他的标识手段来确定类型。一般用不到
- JsonTypeInfo.Id.CLASS: 使用全限定名来识别类型。好处是不用写@JsonSubTypes,缺点是JSON强依赖代码,一旦代码改动,JSON也要跟着变化
- JsonTypeInfo.Id.MINIMAL_CLASS:与JsonTypeInfo.Id.CLASS类似。区别展示可唯一定位一个类的最小路径。例如这里SendEmailAction的最小路径是
"type":".SendEmailAction"。其中"."是不可获取的,表示相对路径的起点 - JsonTypeInfo.Id.NAME:逻辑名称,配合@JsonSubTypes可以实现自定义标识
- JsonTypeInfo.Id.CUSTOM: 表示类型化机制使用自定义处理,需要自定义指向Jackson中的类型化机制
TypeReference
- TypeReference类是Jackson库中的一个类,它可以帮助我们保留泛型信息
- 在下面的代码中,我们定义了一个User类和一个Response类。我们使用ObjectMapper将Response对象序列化为JSON字符串,并使用TypeReference类将JSON字符串反序列化为Response对象,我们就可以正确地反序列化Response对象,并访问其中的数据
javapublic class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class Response<T> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; } } public class Main { public static void main(String[] args) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); Response<User> response = new Response<>(); response.setData(new User("Alice", 25)); String json = mapper.writeValueAsString(response); System.out.println(json); Response<User> deserializedResponse = mapper.readValue(json, new TypeReference<Response<User>>() {}); System.out.println(deserializedResponse.getData().getName()); } }