在对含有 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@AllArgsConstructorprivate static class People {private String name;private Integer age;private Object hobby;}@Getter@Setter@NoArgsConstructor@AllArgsConstructorprivate 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@AllArgsConstructorprivate static class People {private String name;private Integer age;private Sex sex;private Object hobby; // 如果这个object是数组类型,也是会记录其原始类型的}@Getter@Setter@NoArgsConstructor@AllArgsConstructorprivate static class Hobby {private String type;}private interface Sex {String getSex();}private static class Man implements Sex {private String sex;@Overridepublic String getSex() {return sex;}}private static class Woman implements Sex {private String sex;@Overridepublic 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@Setterprivate 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@Setterprivate 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@Setterprivate static class Man implements Sex {private String sex;}@Getter@Setterprivate 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@Setterprivate 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
这个类型无法直接使用,需要提供自定义的解析器才可以配合使用,不提供则会抛出异常。
