在对含有 Object 类型的实例进行序列化、反序列化时,Jackson 默认不会保存 Object 的原始类型,因此在反序列化 Object 时,可能会和原始的 Object 类型不同,具体如下所示:

  1. public class JacksonTest {
  2. public static void main(String[] args) throws JsonProcessingException {
  3. ObjectMapper mapper = new ObjectMapper();
  4. People people = new People("allen", 25, new Hobby("basketball"));
  5. People json = mapper.readValue(mapper.writeValueAsString(people), People.class);
  6. System.out.println(json.hobby instanceof Hobby);
  7. }
  8. @Getter
  9. @Setter
  10. @NoArgsConstructor
  11. @AllArgsConstructor
  12. private static class People {
  13. private String name;
  14. private Integer age;
  15. private Object hobby;
  16. }
  17. @Getter
  18. @Setter
  19. @NoArgsConstructor
  20. @AllArgsConstructor
  21. private static class Hobby {
  22. private String type;
  23. }
  24. }

通过查看反序列化后的 People 实例发现其 hobby 属性已经不是序列化前的 Hobby 类型了。
image.png
为此,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 类型记录其原始类型,包括没有显示类型的泛型。

  1. public static void main(String[] args) throws JsonProcessingException {
  2. ObjectMapper mapper = new ObjectMapper()
  3. .activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
  4. People people = new People("allen", 25, new Hobby("basketball"));
  5. People json = mapper.readValue(mapper.writeValueAsString(people), People.class);
  6. System.out.println(json.hobby instanceof Hobby);
  7. }

运行结果:
image.png
查看其序列化后的 json 数据可以发现,Jackson 把 Object 的原始类型也保存了下来,因此在反序列化的时候会按照原始类型进行还原。

  1. {
  2. "name": "allen",
  3. "age": 25,
  4. "hobby": [
  5. "com.example.demo.JacksonTest$Hobby",
  6. {
  7. "type": "basketball"
  8. }
  9. ]
  10. }

2. OBJECT_AND_NON_CONCRETE

除了 JAVALANG_OBJECT _的特征,当需要对接口、抽象类进行序列化和反序列化时会记录接口、抽象类的原始类型。

  1. public class JacksonTwoTest {
  2. public static void main(String[] args) throws JsonProcessingException {
  3. ObjectMapper mapper = new ObjectMapper()
  4. .activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
  5. People people = new People("allen", 25, new Man(), new Hobby("basketball"));
  6. String str = mapper.writeValueAsString(people);
  7. System.out.println(str);
  8. People json = mapper.readValue(str, People.class);
  9. System.out.println(json.getSex() instanceof Man); // 输出结果为:true
  10. }
  11. @Getter
  12. @Setter
  13. @NoArgsConstructor
  14. @AllArgsConstructor
  15. private static class People {
  16. private String name;
  17. private Integer age;
  18. private Sex sex;
  19. private Object hobby; // 如果这个object是数组类型,也是会记录其原始类型的
  20. }
  21. @Getter
  22. @Setter
  23. @NoArgsConstructor
  24. @AllArgsConstructor
  25. private static class Hobby {
  26. private String type;
  27. }
  28. private interface Sex {
  29. String getSex();
  30. }
  31. private static class Man implements Sex {
  32. private String sex;
  33. @Override
  34. public String getSex() {
  35. return sex;
  36. }
  37. }
  38. private static class Woman implements Sex {
  39. private String sex;
  40. @Override
  41. public String getSex() {
  42. return sex;
  43. }
  44. }
  45. }

image.png
查看其序列化后的 json 数据可以发现,Jackson 把 Object 及 Sex 接口的原始类型也保存了下来,因此在反序列化的时候会按照原始类型进行还原。

  1. {
  2. "name": "allen",
  3. "age": 25,
  4. "sex": [
  5. "com.example.demo.JacksonTwoTest$Man",
  6. {
  7. "sex": null
  8. }
  9. ],
  10. "hobby": [
  11. "com.example.demo.JacksonTwoTest$Hobby",
  12. {
  13. "type": "basketball"
  14. }
  15. ]
  16. }
  17. // object为数组
  18. {
  19. "name": "allen",
  20. "age": 25,
  21. "sex": [
  22. "com.example.demo.JacksonThreeTest$Man",
  23. {
  24. "sex": null
  25. }
  26. ],
  27. "hobby": [
  28. "[Lcom.example.demo.JacksonThreeTest$Hobby;",
  29. [
  30. {
  31. "type": "basketball"
  32. }
  33. ]
  34. ]
  35. }

3. NON_CONCRETE_AND_ARRAYS

4. NON_FINAL

对所有非 final 类型修饰的类及数组记录其类型信息,少数类型(字符串,布尔值,整数,双精度)除外,因为这些类型可以从 JSON 中正确推断出来;以及所有非最终类型的数组。

代码示例同上,查看序列化结果:

  1. [
  2. "com.example.demo.JacksonThreeTest$People",
  3. {
  4. "name": "allen",
  5. "age": 25,
  6. "sex": [
  7. "com.example.demo.JacksonThreeTest$Man",
  8. {
  9. "sex": null
  10. }
  11. ],
  12. "hobby": [
  13. "[Lcom.example.demo.JacksonThreeTest$Hobby;",
  14. [
  15. [
  16. "com.example.demo.JacksonThreeTest$Hobby",
  17. {
  18. "type": "basketball"
  19. }
  20. ],
  21. [
  22. "com.example.demo.JacksonThreeTest$Hobby",
  23. {
  24. "type": "football"
  25. }
  26. ]
  27. ]
  28. ]
  29. }
  30. ]

5. EVERYTHING

不建议使用!

@JsonTypeInfo

@JsonTypeInfo 也是 Jackson 多态类型绑定的一种方式,作用于类或接口,其中 use 属性用来定义使用哪一种类型识别码,它一共支持下面 5 种类型的取值。

  1. @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
  2. @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
  3. @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
  4. @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
  5. @JsonTypeInfo(use = JsonTypeInfo.Id.COSTOM)

1. JsonTypeInfo.Id.NONE

这种类型的输出结果不使用识别码,只有相关参数的值,没有其他类型信息。

  1. private static class People {
  2. private String name;
  3. private Integer age;
  4. @JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
  5. private Object hobby;
  6. }
  7. private static class Hobby {
  8. private String type;
  9. }
  10. // 输出结果:
  11. {
  12. "name": "allen",
  13. "age": 25,
  14. "hobby": {
  15. "type": "basketball"
  16. }
  17. }

2. JsonTypeInfo.Id.CLASS

这种类型的输出结果中,会针对 Object 类型接口抽象类等携带相关的类型信息,通过 @class 键来指定具体类型,格式是标准的 Java 类名称。在反序列化时如果使用了 JsonTypeInfo.Id.CLASS 修饰的话,可以进行类型还原。

  1. private static class People {
  2. private String name;
  3. private Integer age;
  4. @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
  5. private Sex sex;
  6. @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
  7. private Object hobby;
  8. }
  9. // 输出结果:
  10. {
  11. "name": "allen",
  12. "age": 25,
  13. "sex": {
  14. "@class": "org.xl.utils.jackson.annotation.JsonTypeInfoTest$Man",
  15. "sex": null
  16. },
  17. "hobby": {
  18. "@class": "org.xl.utils.jackson.annotation.JsonTypeInfoTest$Hobby",
  19. "type": "basketball"
  20. }
  21. }

可以通过 property 来自定义类型键,以及通过 include 属性来自定义类型键的位置:

  • JsonTypeInfo.As.PROPERTY:作为数据的兄弟属性
  • JsonTypeInfo.As.EXISTING_PROPERTY:作为 POJO 中已经存在的属性
  • JsonTypeInfo.As.EXTERNAL_PROPERTY:作为扩展属性
  • JsonTypeInfo.As.WRAPPER_OBJECT:作为一个包装的对象
  • JsonTypeInfo.As.WRAPPER_ARRAY:作为一个包装的数组

如下示例:

  1. @Getter
  2. @Setter
  3. private static class User {
  4. private String name;
  5. @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_OBJECT)
  6. private Sex sex;
  7. @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "flag")
  8. private Object hobby;
  9. }
  10. // 序列化后的结果
  11. {
  12. "name": "张三",
  13. "sex": {
  14. "org.xl.utils.jackson.annotation.JsonTypeInfoTest$Man": {
  15. "sex": "男"
  16. }
  17. },
  18. "hobby": {
  19. "flag": "org.xl.utils.jackson.annotation.JsonTypeInfoTest$Hobby",
  20. "type": "basketball"
  21. }
  22. }

3. JsonTypeInfo.Id.MINIMAL_CLASS

这种类型的输出结果也携带了相关的类型信息,和 JsonTypeInfo.Id.CLASS 的区别在于 @class 变成了 @c 符号,且存储的 Java 类名称变成了一个更短的类名称(若基类和子类在同一包路径下,会忽略包名)。在反序列化时使用 JsonTypeInfo.Id.MINIMAL_CLASS 修饰的话可以进行类型还原。

  1. {
  2. "name": "allen",
  3. "age": 25,
  4. "sex": {
  5. "@c": ".JacksonThreeTest$Man",
  6. "sex": null
  7. },
  8. "hobby": {
  9. "@c": "org.xl.utils.jackson.annotation.JsonTypeInfoTest$Hobby",
  10. "type": "basketball"
  11. }
  12. }

也可以通过 property 来自定义类型键,默认是 @c 符号。

4. JsonTypeInfo.Id.NAME

这种类型的输出结果只携带了精简的类名称,但在反序列化时是不可以利用的。也可以通过 property 来自定义类型键,默认是 @type 符号。

  1. {
  2. "name": "allen",
  3. "age": 25,
  4. "sex": {
  5. "@type": "JacksonThreeTest$Man",
  6. "sex": null
  7. },
  8. "hobby": {
  9. "@type": "JacksonThreeTest$Hobby",
  10. "type": "basketball"
  11. }
  12. }

此外,我们还可通过 @JsonSubTypes 以及 �@JsonTypeName 注解配合使用,来完成对子类的检测。通常的用法是在 @JsonSubTypes 注解中定义好所有子类类型以及每个子类类型的名称,子类类型名称通过 @JsonTypeInfo 中的 property 属性来指定,如果未设置 property 则需要在 @JsonTypeName 注解上来指定。

具体示例如下,设置了自定义的 property 属性:

  1. @Getter
  2. @Setter
  3. private static class User {
  4. private String name;
  5. @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
  6. private Sex sex;
  7. }
  8. @JsonSubTypes({
  9. @JsonSubTypes.Type(value = Man.class, name = "JsonSubTypesTest$Man"),
  10. @JsonSubTypes.Type(value = Woman.class, name = "JsonSubTypesTest$Woman")})
  11. private interface Sex {
  12. String getSex();
  13. void setSex(String sex);
  14. }
  15. @Getter
  16. @Setter
  17. private static class Man implements Sex {
  18. private String sex;
  19. }
  20. @Getter
  21. @Setter
  22. private static class Woman implements Sex {
  23. private String sex;
  24. }

对其进行反序列化:

  1. public static void main(String[] args) throws JsonProcessingException {
  2. User userD = MAPPER.readValue("{\"name\":\"张三\",\"sex\":{\"type\":\"JsonSubTypesTest$Man\",\"sex\":\"男\"}}", User.class);
  3. System.out.println(userD.getSex().getClass().getName());
  4. }
  5. // 输出结果:org.xl.utils.jackson.annotation.JsonSubTypesTest$Man

我们也可以不指定 property 属性,通过 @JsonTypeName 注解指定的名称来匹配对应子类:

  1. @Getter
  2. @Setter
  3. private static class User {
  4. private String name;
  5. @JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
  6. private Sex sex;
  7. }
  8. @JsonSubTypes({
  9. @JsonSubTypes.Type(value = Man.class, name = "Man"),
  10. @JsonSubTypes.Type(value = Woman.class, name = "Woman")})
  11. private interface Sex {
  12. void setSex(String sex);
  13. }
  14. @Getter
  15. @Setter
  16. @JsonTypeName("Man")
  17. private static class Man implements Sex {
  18. private String sex;
  19. }
  20. @Getter
  21. @Setter
  22. @JsonTypeName("Woman")
  23. private static class Woman implements Sex {
  24. private String sex;
  25. }

对其进行序列化和反序列化得到:

  1. public static void main(String[] args) throws JsonProcessingException {
  2. User user = new User();
  3. user.setName("张三");
  4. Sex sex = new Man();
  5. sex.setSex("男");
  6. user.setSex(sex);
  7. System.out.println(MAPPER.writeValueAsString(user));
  8. User userD = MAPPER.readValue("{\"name\":\"张三\",\"sex\":{\"@type\":\"Man\",\"sex\":\"男\"}}", User.class);
  9. System.out.println(userD.getSex().getClass().getName());
  10. }
  11. // 输出结果:
  12. {"name":"张三","sex":{"@type":"Man","sex":"男"}}
  13. org.xl.utils.jackson.annotation.JsonSubTypesTest$Man

5. JsonTypeInfo.Id.COSTOM

这个类型无法直接使用,需要提供自定义的解析器才可以配合使用,不提供则会抛出异常。