在对含有 Object 类型的实例进行序列化、反序列化时,Jackson 默认不会保存 Object 的原始类型,因此在反序列化 Object 时,可能会和原始的 Object 类型不同,具体如下所示:
public class JacksonTest {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
People people = new People("allen", 25, new Hobby("basketball"));
People json = mapper.readValue(mapper.writeValueAsString(people), People.class);
System.out.println(json.hobby instanceof Hobby);
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
private static class People {
private String name;
private Integer age;
private Object hobby;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
private static class Hobby {
private String type;
}
}
通过查看反序列化后的 People 实例发现其 hobby 属性已经不是序列化前的 Hobby 类型了。
为此,Jackson 提供了一种多态类型绑定的机制:JacksonPolymorphicDeserialization。文档中提供了两种使用方式,一种是 Global default typing,另一种是 @JsonTypeInfo 注解,下面分别讲解如何使用。
Global default typing
通过这种方式,我们可以对创建出来的 ObjectMapper 进行全局设置,同时这个全局设置也可以由每个类的注释进行覆盖。DefaultTyping 枚举有五个值(截止 2.11.4 版本),分别看下作用:
- JAVA_LANG_OBJECT
- OBJECT_AND_NON_CONCRETE
- NON_CONCRETE_AND_ARRAYS
- NON_FINAL
- EVERYTHING
1. JAVA_LANG_OBJECT
在进行序列化和反序列化时,只针对 Object 类型记录其原始类型,包括没有显示类型的泛型。
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper()
.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
People people = new People("allen", 25, new Hobby("basketball"));
People json = mapper.readValue(mapper.writeValueAsString(people), People.class);
System.out.println(json.hobby instanceof Hobby);
}
运行结果:
查看其序列化后的 json 数据可以发现,Jackson 把 Object 的原始类型也保存了下来,因此在反序列化的时候会按照原始类型进行还原。
{
"name": "allen",
"age": 25,
"hobby": [
"com.example.demo.JacksonTest$Hobby",
{
"type": "basketball"
}
]
}
2. OBJECT_AND_NON_CONCRETE
除了 JAVALANG_OBJECT _的特征,当需要对接口、抽象类进行序列化和反序列化时会记录接口、抽象类的原始类型。
public class JacksonTwoTest {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper()
.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
People people = new People("allen", 25, new Man(), new Hobby("basketball"));
String str = mapper.writeValueAsString(people);
System.out.println(str);
People json = mapper.readValue(str, People.class);
System.out.println(json.getSex() instanceof Man); // 输出结果为:true
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
private static class People {
private String name;
private Integer age;
private Sex sex;
private Object hobby; // 如果这个object是数组类型,也是会记录其原始类型的
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
private static class Hobby {
private String type;
}
private interface Sex {
String getSex();
}
private static class Man implements Sex {
private String sex;
@Override
public String getSex() {
return sex;
}
}
private static class Woman implements Sex {
private String sex;
@Override
public String getSex() {
return sex;
}
}
}
查看其序列化后的 json 数据可以发现,Jackson 把 Object 及 Sex 接口的原始类型也保存了下来,因此在反序列化的时候会按照原始类型进行还原。
{
"name": "allen",
"age": 25,
"sex": [
"com.example.demo.JacksonTwoTest$Man",
{
"sex": null
}
],
"hobby": [
"com.example.demo.JacksonTwoTest$Hobby",
{
"type": "basketball"
}
]
}
// object为数组
{
"name": "allen",
"age": 25,
"sex": [
"com.example.demo.JacksonThreeTest$Man",
{
"sex": null
}
],
"hobby": [
"[Lcom.example.demo.JacksonThreeTest$Hobby;",
[
{
"type": "basketball"
}
]
]
}
3. NON_CONCRETE_AND_ARRAYS
4. NON_FINAL
对所有非 final 类型修饰的类及数组记录其类型信息,少数类型(字符串,布尔值,整数,双精度)除外,因为这些类型可以从 JSON 中正确推断出来;以及所有非最终类型的数组。
代码示例同上,查看序列化结果:
[
"com.example.demo.JacksonThreeTest$People",
{
"name": "allen",
"age": 25,
"sex": [
"com.example.demo.JacksonThreeTest$Man",
{
"sex": null
}
],
"hobby": [
"[Lcom.example.demo.JacksonThreeTest$Hobby;",
[
[
"com.example.demo.JacksonThreeTest$Hobby",
{
"type": "basketball"
}
],
[
"com.example.demo.JacksonThreeTest$Hobby",
{
"type": "football"
}
]
]
]
}
]
5. EVERYTHING
@JsonTypeInfo
@JsonTypeInfo 也是 Jackson 多态类型绑定的一种方式,作用于类或接口,其中 use 属性用来定义使用哪一种类型识别码,它一共支持下面 5 种类型的取值。
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonTypeInfo(use = JsonTypeInfo.Id.COSTOM)
1. JsonTypeInfo.Id.NONE
这种类型的输出结果不使用识别码,只有相关参数的值,没有其他类型信息。
private static class People {
private String name;
private Integer age;
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
private Object hobby;
}
private static class Hobby {
private String type;
}
// 输出结果:
{
"name": "allen",
"age": 25,
"hobby": {
"type": "basketball"
}
}
2. JsonTypeInfo.Id.CLASS
这种类型的输出结果中,会针对 Object 类型、接口、抽象类等携带相关的类型信息,通过 @class 键来指定具体类型,格式是标准的 Java 类名称。在反序列化时如果使用了 JsonTypeInfo.Id.CLASS 修饰的话,可以进行类型还原。
private static class People {
private String name;
private Integer age;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
private Sex sex;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
private Object hobby;
}
// 输出结果:
{
"name": "allen",
"age": 25,
"sex": {
"@class": "org.xl.utils.jackson.annotation.JsonTypeInfoTest$Man",
"sex": null
},
"hobby": {
"@class": "org.xl.utils.jackson.annotation.JsonTypeInfoTest$Hobby",
"type": "basketball"
}
}
可以通过 property 来自定义类型键,以及通过 include 属性来自定义类型键的位置:
- JsonTypeInfo.As.PROPERTY:作为数据的兄弟属性
- JsonTypeInfo.As.EXISTING_PROPERTY:作为 POJO 中已经存在的属性
- JsonTypeInfo.As.EXTERNAL_PROPERTY:作为扩展属性
- JsonTypeInfo.As.WRAPPER_OBJECT:作为一个包装的对象
- JsonTypeInfo.As.WRAPPER_ARRAY:作为一个包装的数组
如下示例:
@Getter
@Setter
private static class User {
private String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_OBJECT)
private Sex sex;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "flag")
private Object hobby;
}
// 序列化后的结果
{
"name": "张三",
"sex": {
"org.xl.utils.jackson.annotation.JsonTypeInfoTest$Man": {
"sex": "男"
}
},
"hobby": {
"flag": "org.xl.utils.jackson.annotation.JsonTypeInfoTest$Hobby",
"type": "basketball"
}
}
3. JsonTypeInfo.Id.MINIMAL_CLASS
这种类型的输出结果也携带了相关的类型信息,和 JsonTypeInfo.Id.CLASS 的区别在于 @class 变成了 @c 符号,且存储的 Java 类名称变成了一个更短的类名称(若基类和子类在同一包路径下,会忽略包名)。在反序列化时使用 JsonTypeInfo.Id.MINIMAL_CLASS 修饰的话可以进行类型还原。
{
"name": "allen",
"age": 25,
"sex": {
"@c": ".JacksonThreeTest$Man",
"sex": null
},
"hobby": {
"@c": "org.xl.utils.jackson.annotation.JsonTypeInfoTest$Hobby",
"type": "basketball"
}
}
也可以通过 property 来自定义类型键,默认是 @c 符号。
4. JsonTypeInfo.Id.NAME
这种类型的输出结果只携带了精简的类名称,但在反序列化时是不可以利用的。也可以通过 property 来自定义类型键,默认是 @type 符号。
{
"name": "allen",
"age": 25,
"sex": {
"@type": "JacksonThreeTest$Man",
"sex": null
},
"hobby": {
"@type": "JacksonThreeTest$Hobby",
"type": "basketball"
}
}
此外,我们还可通过 @JsonSubTypes 以及 �@JsonTypeName 注解配合使用,来完成对子类的检测。通常的用法是在 @JsonSubTypes 注解中定义好所有子类类型以及每个子类类型的名称,子类类型名称通过 @JsonTypeInfo 中的 property 属性来指定,如果未设置 property 则需要在 @JsonTypeName 注解上来指定。
具体示例如下,设置了自定义的 property 属性:
@Getter
@Setter
private static class User {
private String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
private Sex sex;
}
@JsonSubTypes({
@JsonSubTypes.Type(value = Man.class, name = "JsonSubTypesTest$Man"),
@JsonSubTypes.Type(value = Woman.class, name = "JsonSubTypesTest$Woman")})
private interface Sex {
String getSex();
void setSex(String sex);
}
@Getter
@Setter
private static class Man implements Sex {
private String sex;
}
@Getter
@Setter
private static class Woman implements Sex {
private String sex;
}
对其进行反序列化:
public static void main(String[] args) throws JsonProcessingException {
User userD = MAPPER.readValue("{\"name\":\"张三\",\"sex\":{\"type\":\"JsonSubTypesTest$Man\",\"sex\":\"男\"}}", User.class);
System.out.println(userD.getSex().getClass().getName());
}
// 输出结果:org.xl.utils.jackson.annotation.JsonSubTypesTest$Man
我们也可以不指定 property 属性,通过 @JsonTypeName 注解指定的名称来匹配对应子类:
@Getter
@Setter
private static class User {
private String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
private Sex sex;
}
@JsonSubTypes({
@JsonSubTypes.Type(value = Man.class, name = "Man"),
@JsonSubTypes.Type(value = Woman.class, name = "Woman")})
private interface Sex {
void setSex(String sex);
}
@Getter
@Setter
@JsonTypeName("Man")
private static class Man implements Sex {
private String sex;
}
@Getter
@Setter
@JsonTypeName("Woman")
private static class Woman implements Sex {
private String sex;
}
对其进行序列化和反序列化得到:
public static void main(String[] args) throws JsonProcessingException {
User user = new User();
user.setName("张三");
Sex sex = new Man();
sex.setSex("男");
user.setSex(sex);
System.out.println(MAPPER.writeValueAsString(user));
User userD = MAPPER.readValue("{\"name\":\"张三\",\"sex\":{\"@type\":\"Man\",\"sex\":\"男\"}}", User.class);
System.out.println(userD.getSex().getClass().getName());
}
// 输出结果:
{"name":"张三","sex":{"@type":"Man","sex":"男"}}
org.xl.utils.jackson.annotation.JsonSubTypesTest$Man
5. JsonTypeInfo.Id.COSTOM
这个类型无法直接使用,需要提供自定义的解析器才可以配合使用,不提供则会抛出异常。