封面来源:碧蓝航线 箱庭疗法 活动CG
本文涉及的代码:mofan212/jackson-study 
Jackson 源码仓库:
1. Jackson 概述 
1.1 什么是 Jackson 
Jackson 是一个用来处理 JSON 格式数据的 Java 类库,一个用来序列化和反序列化 JSON 的 Java 开源框架。从 GitHub 中的统计来看,Jackson 是最流行的 JSON 解析器之一。SpringMVC 的默认 JSON 解析器就是 Jackson。
GitHub 仓库对 Jackson 的介绍:
Jackson 作为 Java 中用来处理 JSON 数据的类库而熟知,但它并不局限于 JSON。 
1.2 Jackson 核心模块 
Jackson 的核心模块是扩展(模块)所基于的基础。从 Jackson 2.x 开始,核心模块主要由三部分组成:
jackson-core 核心包,提供基于“流模式”解析的相关 API,它包括 JsonPaser 和 JsonGenerator。 Jackson 内部实现正是通过高性能的流模式 API 的 JsonGenerator 和 JsonParser 来生成和解析 JSON。 
jackson-annotations 注解包,提供标准注解功能。 
jackson-databind 数据绑定包, 提供基于“对象绑定”解析的相关 API(ObjectMapper)和“树模型”解析的相关 API(JsonNode)。基于“对象绑定”解析的 API 和“树模型”解析的 API 依赖基于“流模式”解析的 API。 
 
关于核心模块,GitHub 上是这么说的:
jackson-databind 依赖 jackson-core 和 jackson-annotations,添加 jackson-databind 之后, jackson-core 和 jackson-annotations 也随之添加到项目工程中了。
因此,在 Maven 工程中导入依赖时,导入 jackson-databind 即可。
2. 快速开始 
2.1 ObjectMapper 的使用 
创建 Maven 项目,引入 Jackson 的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 <properties >     <maven.compiler.source > 17</maven.compiler.source >      <maven.compiler.target > 17</maven.compiler.target >      <jackson.version > 2.15.2</jackson.version >  </properties > <dependencies >     <dependency >          <groupId > com.fasterxml.jackson.core</groupId >          <artifactId > jackson-databind</artifactId >          <version > ${jackson.version}</version >      </dependency >  </dependencies > 
 
编写一个简单的 Java 实体类:
1 2 3 4 5 6 7 8 9 10 11 12 @Getter @Setter @NoArgsConstructor @AllArgsConstructor public  class  Entity  {    private  String name;     private  int  age;     private  double  height; } 
 
使用 Jackson 对实体进行序列化,或者反序列化成实体,需要实体中有无参构造方法、Getter 和 Setter 方法。Getter 方法用于序列化,Setter 方法用于反序列化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test @SneakyThrows public  void  testObjectMapper ()  {    ObjectMapper  mapper  =  new  ObjectMapper ().enable(SerializationFeature.INDENT_OUTPUT);     User  user  =  new  User ("mofan" , 18 , 177 );          String  string  =  mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);     String  expectJson  =  """              {               "name" : "mofan",               "age" : 18,               "height" : 177.0             }             """ ;    JsonAssertions.assertThatJson(string).isEqualTo(expectJson);          User  readValue  =  mapper.readValue(string, User.class);     assertThat(readValue).extracting(User::getName, User::getAge, User::getHeight)             .containsExactly("mofan" , 18 , 177.0 ); } 
 
writerWithDefaultPrettyPrinter() 方法的作用是 格式化  输出的 JSON 字符串。如果想要全局启用,可以在 ObjectMapper 类上进行设置,如:
1 ObjectMapper  mapper  =  new  ObjectMapper ().enable(SerializationFeature.INDENT_OUTPUT);
 
2.2 JSON 来源 
使用 ObjectMapper 对象对 JSON 进行序列化和反序列时,JSON 来源有很多种,readValue() 重载的方法参数有以下七种:
1 String | File | URL | InputStream | byte[] | DataInput | JsonParser 
 
2.3 简单 JSON 串的读取 
简单的反序列化
 
存在名为 /json/user.json 的文件,其内容为:
1 2 3 4 5 {   "name"  :  "mofan" ,    "age"  :  18 ,    "height"  :  178.1  } 
 
尝试将不同来源的 JSON 信息序列化为 Java 对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Test @SneakyThrows public  void  testDeserialization_1 ()  {    ObjectMapper  mapper  =  new  ObjectMapper ().enable(SerializationFeature.INDENT_OUTPUT);     URL  url  =  getClass().getClassLoader().getResource("./json/user.json" );     File  file  =  new  File (url.getFile());          User  user1  =  mapper.readValue(file, User.class);     assertThat(user1).extracting(User::getName, User::getAge, User::getHeight)             .containsExactly("mofan" , 18 , 178.1 );     String  userJson  =  """              {               "name": "默烦",               "age": 20,               "height": 177.5             }             """ ;         User  user2  =  mapper.readValue(userJson, User.class);     assertThat(user2).extracting(User::getName, User::getAge, User::getHeight)             .containsExactly("默烦" , 20 , 177.5 );          User  user3  =  mapper.readValue(new  StringReader (userJson), User.class);     assertThat(user3).extracting(User::getName, User::getAge, User::getHeight)             .containsExactly("默烦" , 20 , 177.5 );          User  user4  =  mapper.readValue(new  FileInputStream (file), User.class);     assertThat(user4).extracting(User::getName, User::getAge, User::getHeight)             .containsExactly("mofan" , 18 , 178.1 );          User  user5  =  mapper.readValue(userJson.getBytes(StandardCharsets.UTF_8), User.class);     assertThat(user5).extracting(User::getName, User::getAge, User::getHeight)             .containsExactly("默烦" , 20 , 177.5 ); } 
 
解析成对象数组、列表、映射
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @Test @SneakyThrows public  void  testDeserialization_2 ()  {    ObjectMapper  mapper  =  new  ObjectMapper ().enable(SerializationFeature.INDENT_OUTPUT);     String  userJson  =  """              {               "name": "默烦",               "age": 20,               "height": 177.5             }             """ ;    String  jsonArray  =  """              [               {                 "name": "mofan"               },               {                 "name": "默烦"               }             ]             """ ;         User[] users1 = mapper.readValue(jsonArray, User[].class);     assertThat(users1).extracting(User::getName, User::getAge, User::getHeight)             .containsExactly(tuple("mofan" , 0 , 0.0 ), tuple("默烦" , 0 , 0.0 ));          List<User> userList = mapper.readValue(jsonArray, new  TypeReference <>() {     });     assertThat(userList).extracting(User::getName, User::getAge, User::getHeight)             .containsExactly(tuple("mofan" , 0 , 0.0 ), tuple("默烦" , 0 , 0.0 ));          Map<String, Object> map = mapper.readValue(userJson, new  TypeReference <>() {     });     assertThat(map).containsAllEntriesOf(Map.of("age" , 20 , "name" , "默烦" , "height" , 177.5 )); } 
 
2.4 树模型 
前面介绍了简单 JSON 串的读取,如果一个 JSON 串比较复杂,内部还有数组,或者嵌套了其他 JSON 串,这个时候使用前面的方式就无法解析了。为了解决这种复杂情况(开发中经常经常出现的情况),Jackson 引入了树模型的概念,涉及的类有 JsonNode、ObjectNode 等等。
解析成 JsonNode
 
将 JSON 字符串解析成 JsonNode 有两种方式,可以使用 readValue() 指定第二个参数为 JsonNode.class,也可以使用 readTree() 方法,这个方法总是会返回一个 JsonNode 对象。
在 ObjectMapper 类中 readTree() 方法有多种重载,能将多种格式的 JSON 数据解析成 JsonNode:
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Test @SneakyThrows public  void  testParseJSONIntoJsonNode ()  {    String  userJson  =  """              {               "name": "默烦",               "age": 20,               "height": 177.5             }             """ ;    ObjectMapper  mapper  =  new  ObjectMapper ();     JsonNode  jsonNode1  =  mapper.readValue(userJson, JsonNode.class);     assertThat(jsonNode1.get("name" ).asText()).isEqualTo("默烦" );     JsonNode  jsonNode2  =  mapper.readTree(userJson);     assertThat(jsonNode2.get("age" ).asInt()).isEqualTo(20 );     ObjectNode  objectNode  =  ((ObjectNode) mapper.readTree(userJson));     objectNode.withObject("/other" ).put("type" , "Student" );     String  string  =  mapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode);     String  expectJson  =  """              {               "name" : "默烦",               "age" : 20,               "height" : 177.5,               "other" : {                 "type" : "Student"               }             }             """ ;    JsonAssertions.assertThatJson(string).isEqualTo(expectJson); } 
 
JsonNode 的基本操作
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @Test @SneakyThrows public  void  testJsonNodeOperation ()  {    String  userJson  =  """              {               "name": "mofan",               "age": 20,               "hobby": [                 "music",                 "game",                 "study"               ],               "other": {                 "type": "Student"               }             }                             """ ;    ObjectMapper  mapper  =  new  ObjectMapper ();     JsonNode  jsonNode  =  mapper.readTree(userJson);          String  name  =  jsonNode.get("name" ).asText();     assertThat(name).isEqualTo("mofan" );          assertThat(jsonNode.get("age" ).asInt()).isEqualTo(20 );          String  firstHobby  =  jsonNode.get("hobby" ).get(0 ).asText();     assertThat(firstHobby).isEqualTo("music" );          String  type  =  jsonNode.get("other" ).get("type" ).asText();     assertThat(type).isEqualTo("Student" ); } 
 
添加与删除
 
使用树模型,甚至还可以向原 JSON 串中添加或删除数据,只不过这个时候不能用 JsonNode 来操作了,得使用它的子类 ObjectNode,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 @Test @SneakyThrows public  void  testAddDataToJSON ()  {    String  userJson  =  """              {               "name": "默烦",               "age": 20,               "height": 177.5             }             """ ;    ObjectMapper  mapper  =  new  ObjectMapper ().enable(SerializationFeature.INDENT_OUTPUT);     ObjectNode  objectNode  =  mapper.readValue(userJson, ObjectNode.class);          objectNode.put("weight" , 65 );          objectNode.withObject("/other" ).put("type" , "Student" );          objectNode.withArray("hobby" ).add("music" ).add("game" ).add("study" );          objectNode.withArray("hobby" ).insert(1 , "write" );          objectNode.withArray("jsonArray" ).add(mapper.readValue(userJson, ObjectNode.class));          objectNode.withArray("objectArray" ).addObject().putObject("Test" ).put("test" , "test" );     String  expectJson  =  """              {               "name" : "默烦",               "age" : 20,               "height" : 177.5,               "weight" : 65,               "other" : {                 "type" : "Student"               },               "hobby" : [ "music", "write", "game", "study" ],               "jsonArray" : [ {                 "name" : "默烦",                 "age" : 20,                 "height" : 177.5               } ],               "objectArray" : [ {                 "Test" : {                   "test" : "test"                 }               } ]             }             """ ;    JsonAssertions.assertThatJson(mapper.writeValueAsString(objectNode)).isEqualTo(expectJson);     assertThat(objectNode.get("objectArray" ).get(0 ).isObject()).isTrue();     assertThat(objectNode.get("weight" ).asInt()).isEqualTo(65 );     assertThat(objectNode.get("other" ).isObject()).isTrue();     assertThat(objectNode.get("other" ).get("type" ).asText()).isEqualTo("Student" );     assertThat(objectNode.get("hobby" ).get(0 ).asText()).isEqualTo("music" );          objectNode.without("age" );          objectNode.withObject("/other" ).without("type" );          objectNode.without("other" );          objectNode.withArray("hobby" ).remove(0 );     objectNode.withArray("jsonArray" ).removeAll();          User  user  =  new  User ("mofan" , 19 , 177.5 );     objectNode.withArray("addPojo" ).addPOJO(user);          objectNode.withArray("addPojoWithField" ).addObject().putPOJO("user" , user);     expectJson = """              {               "name" : "默烦",               "height" : 177.5,               "weight" : 65,               "hobby" : [ "write", "game", "study" ],               "jsonArray" : [ ],               "objectArray" : [ {                 "Test" : {                   "test" : "test"                 }               } ],               "addPojo" : [ {                 "name" : "mofan",                 "age" : 19,                 "height" : 177.5               } ],               "addPojoWithField" : [ {                 "user" : {                   "name" : "mofan",                   "age" : 19,                   "height" : 177.5                 }               } ]             }             """ ;    JsonAssertions.assertThatJson(mapper.writeValueAsString(objectNode)).isEqualTo(expectJson); } 
 
有关 ArrayNode 的方法还有很多,查看源码可以发现,其底层就是 List<JsonNode>,所以使用方式也和动态数组差不多。
比如,可以使用 set() 方法修改某个索引位置的数据,还可以使用 insert() 方法来给某个索引位置添加数据。
JsonNode 与 Object 的转换
 
JsonNode 与 Object 之间是可以进行转换的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Test @SneakyThrows public  void  testTransform ()  {    String  userJson  =  """              {               "name": "默烦",               "age": 20,               "height": 177.5             }             """ ;    ObjectMapper  mapper  =  new  ObjectMapper ().enable(SerializationFeature.INDENT_OUTPUT);          User  user  =  mapper.readValue(userJson, User.class);     assertThat(user).extracting(User::getName, User::getAge, User::getHeight)             .containsExactly("默烦" , 20 , 177.5 );          JsonNode  jsonNode1  =  mapper.readTree(userJson);     User  user1  =  mapper.treeToValue(jsonNode1, User.class);     assertThat(user1).extracting(User::getName, User::getAge, User::getHeight)             .containsExactly("默烦" , 20 , 177.5 );          User  user2  =  new  User ("mofan" , 18 , 178.1 );     JsonNode  jsonNode2  =  mapper.valueToTree(user2);     String  expectJson  =  """              {               "name" : "mofan",               "age" : 18,               "height" : 178.1             }             """ ;    JsonAssertions.assertThatJson(mapper.writeValueAsString(jsonNode2)).isEqualTo(expectJson); } 
 
2.5 转换成 JSON 串 
简单的序列化
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Test @SneakyThrows public  void  testSerialization_1 ()  {    User  user  =  new  User ("默烦" , 19 , 178.2 );     ObjectMapper  mapper  =  new  ObjectMapper ().enable(SerializationFeature.INDENT_OUTPUT);          String  str  =  mapper.writeValueAsString(user);     String  expectJson  =  """              {               "name" : "默烦",               "age" : 19,               "height" : 178.2             }             """ ;    JsonAssertions.assertThatJson(str).isEqualTo(expectJson);          File  file  =  new  File ("test.json" );     mapper.writeValue(file, user);     assertThat(file).exists();          byte [] bytes = mapper.writeValueAsBytes(user);     assertThat(mapper.readValue(bytes, User.class))             .extracting(User::getName, User::getAge, User::getHeight)             .containsExactly("默烦" , 19 , 178.2 ); } 
 
在当前 Module 目录下,可以看到一个生成的名为 test.json 的文件,文件内容为:
1 2 3 4 5 {   "name"  :  "默烦" ,    "age"  :  19 ,    "height"  :  178.2  } 
 
映射转换成 JSON
 
除了可以将实体类序列化成 JSON 字符串外,还可以将 Map 映射转换成 JSON 字符串,JSON 字符串是一种键值关系,而 Map 也是一种键值关系,因此能够相互转换也是情理之中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Test @SneakyThrows public  void  testSerialization_2 ()  {    ObjectMapper  mapper  =  new  ObjectMapper ().enable(SerializationFeature.INDENT_OUTPUT);     Map<String, Object> map = new  HashMap <>(8 );     map.put("name" , "mofan" );     map.put("age" , 18 );     map.put("hobby" , new  String []{"code" , "game" , "study" });     List<String> list = new  ArrayList <>();     list.add("book_1" );     list.add("book_2" );     list.add("book_3" );     map.put("book" , list);     String  str  =  mapper.writeValueAsString(map);     String  expectJson  =  """              {               "name" : "mofan",               "age" : 18,               "hobby" : [ "code", "game", "study" ],               "book" : [ "book_1", "book_2", "book_3" ]             }             """ ;    JsonAssertions.assertThatJson(str).isEqualTo(expectJson); } 
 
3. 流解析器 
3.1 流解析器的概述 
在 GitHub 上,关于 Jackson 的流解析器:
翻译一下就是:
使用数据绑定(实体与 JSON 之间的转换)很方便,树模型也很灵活,但是还有一种更规范的处理模型:增量(流)模型。 它是数据绑定和树模型的基础处理模型 ,它暴露给那些希望获得最高性能、控制解析或生成细节的用户。
3.2 基本使用 
流解析器在使用时就像编写 XML 标签或者 HTML 标签一样,开头一个标识,结尾一个标识,中间书写内容,虽然书写起来很繁琐,但是操作 JSON 的性能和细节都能被编码者所掌握。
先来个 Quick Start:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Test @SneakyThrows public  void  testStreamingParser ()  {    ObjectMapper  mapper  =  new  ObjectMapper ();     JsonFactory  factory  =  mapper.getFactory();     File  file  =  new  File ("simpleJSON.json" );     JsonGenerator  generator  =  factory.createGenerator(file, JsonEncoding.UTF8);     generator.writeStartObject();     generator.writeStringField("msg" , "Hello Jackson" );     generator.writeEndObject();     generator.close();     JsonParser  parser  =  factory.createParser(file);     JsonToken  jsonToken  =  parser.nextToken();     jsonToken = parser.nextToken();     assertThat(parser.getCurrentName()).isEqualTo("msg" );     assertThat(jsonToken).isEqualTo(JsonToken.FIELD_NAME);     jsonToken = parser.nextToken();     assertThat(jsonToken).isEqualTo(JsonToken.VALUE_STRING);     String  msg  =  parser.getText();     assertThat(msg).isEqualTo("Hello Jackson" );     parser.close(); } 
 
同时也能在当前 Module 下看到生成的名为 simpleJSON.json 的文件,其内容为:
 
3.3 写入 JSON 数据 
如果需要写入 JSON 数据,那么需要使用到 JsonGenerator 类,它是定义公共 API 编写的 JSON 内容的基类。也就是说,要写入 JSON 数据,就需要创建一个 JsonGenerator 对象,然后使用这个对象来写入数据。
要创建 JsonGenerator 实例,需要使用 JsonFactory 实例的 createGenerator() 工厂方法来创建实例。这个方法有很多重载,各种重载指定了数据写入的位置和写入的方式:
成功得到 JsonGenerator 实例后,使用实例的各种 writexxxxx() 方法就可以写入数据了。
数据写入完成后,记得调用 close() 方法。当然,更推荐使用 try-with-resources 自动关闭资源。 
来写入一些比 Quick Start 案例中要复杂一些的 JSON 数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @Test @Order(1) @SneakyThrows public  void  testJsonGenerator ()  {    ObjectMapper  mapper  =  new  ObjectMapper ();     JsonFactory  factory  =  mapper.getFactory();     File  file  =  new  File ("testJsonGenerator.json" );     try  (JsonGenerator  generator  =  factory.createGenerator(file, JsonEncoding.UTF8)) {                  generator.writeStartObject();                  generator.writeStringField("name" , "默烦" );                  generator.writeNumberField("age" , 18 );                  generator.writeBooleanField("isGirl" , false );                  generator.writeFieldName("hobby" );                  generator.writeStartArray();                  generator.writeString("code" );         generator.writeString("study" );                  generator.writeEndArray();                  generator.writeEndObject();     }          Map<String, Object> map = mapper.readValue(file, new  TypeReference <>() {     });     assertThat(map).containsAllEntriesOf(Map.of(         "age" , 18 ,         "hobby" , List.of("code" , "study" ),         "isGirl" , false ,         "name" , "默烦" )); } 
 
在当前 Module 下,还会生成名为 testJsonGenerator.json 的文件,格式化后其内容为:
1 2 3 4 5 6 7 8 9 {   "name" :  "默烦" ,    "age" :  18 ,    "isGirl" :  false ,    "hobby" :  [      "code" ,      "study"    ]  } 
 
小总结
 
总的来说,写入数据并不难,编写代码时类似于写 XML 或 HTML 标签,有开始标志,有结束标志,中间写内容,最后记得关闭资源。
3.4 读取 JSON 数据 
参考链接:介绍Jackson JsonParser解析json  | 解析Java的Jackson库中Streaming API的使用 
读取 JSON 数据时,需要使用到 JsonParser 类,它是定义公共 API 用于读取的 JSON 内容的基类。这个类的实例的创建方式和 JsonGenerator 类的实例的创建方式一样,都需要使用 JsonFactory 实例的 createParser() 工厂方法来创建。这个方法也有很多重载,实现了解析不同来源的 JOSN 数据:
创建完 JsonParser 实例后,可以用其解析 JOSN 数据。JsonParser 工作方式是将 JSON 分解成一系列标记(token),逐个迭代遍历标记并进行解析。
试试通过循环所有标记并输出,JSON 数据用前一步生成的 testJsonGenerator.json 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Test @Order(2) @SneakyThrows public  void  testGetToken ()  {    JsonFactory  jsonFactory  =  new  JsonFactory ();          File  file  =  new  File ("testJsonGenerator.json" );     JsonParser  parser  =  jsonFactory.createParser(file);     List<JsonToken> list = new  ArrayList <>();     while  (!parser.isClosed()) {         list.add(parser.nextToken());     }     assertThat(list).containsExactly(             JsonToken.START_OBJECT,             JsonToken.FIELD_NAME,             JsonToken.VALUE_STRING,             JsonToken.FIELD_NAME,             JsonToken.VALUE_NUMBER_INT,             JsonToken.FIELD_NAME,             JsonToken.VALUE_FALSE,             JsonToken.FIELD_NAME,             JsonToken.START_ARRAY,             JsonToken.VALUE_STRING,             JsonToken.VALUE_STRING,             JsonToken.END_ARRAY,             JsonToken.END_OBJECT,             null      ); } 
 
对比 list 与原 JSON 信息:
 
这样一对比后,那不是直接清楚明了了?😎
既然如此,来试试读取数据,依旧是读取 testJsonGenerator.json 文件中的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Test @Order(3) @SneakyThrows public  void  testJsonParser ()  {    ObjectMapper  mapper  =  new  ObjectMapper ();     JsonFactory  factory  =  mapper.getFactory();     File  file  =  new  File ("testJsonGenerator.json" );     JsonParser  parser  =  factory.createParser(file);     while  (!parser.isClosed()) {         if  (JsonToken.FIELD_NAME.equals(parser.nextToken())) {             String  fieldName  =  parser.getCurrentName();                          parser.nextToken();             if  ("name" .equals(fieldName)) {                 assertThat(parser.getValueAsString()).isEqualTo("默烦" );             }             if  ("age" .equals(fieldName)) {                 assertThat(parser.getValueAsInt()).isEqualTo(18 );             }             if  ("isGirl" .equals(fieldName)) {                 assertThat(parser.getValueAsBoolean()).isFalse();             }             if  ("hobby" .equals(fieldName)) {                 List<String> list = new  ArrayList <>();                 while  (parser.nextToken() != JsonToken.END_ARRAY) {                     list.add(parser.getValueAsString());                 }                 assertThat(list).containsExactly("code" , "study" );             }         }     } } 
 
如果标记指针指向的是字段,JsonParser 的 getCurrentName() 方法返回当前字段名称。
JsonParser 的 getValueAsString() 返回当前标记值的字符串类型,同理 getValueAsInt() 返回整型值,同样还有 Boolean、Double、Long 三种类型可以选择。
4. Jackson 的配置 
4.1 配置前言 
Jackson 有两种入门级配置机制,分别是:Features 和 Annotations,前者可以翻译成特征,比如在最开始使用的 enable(SerializationFeature.INDENT_OUTPUT) 就属于 Features,后者是注解的意思,可以在某些地方添加注解,达到想要的目标。
注意:  Jackson 遵循 configure-first-then-use ,即“先配置再使用”,也就是说在进行了 首次  序列化或反序列化操作后,再对 Jackson 进行的配置都不会生效,尽管这些配置在运行过程中并不会抛出异常。
4.2 常用的 Features 
在 Jackson 的 GitHub 上,关于 Commonly used Features 有这些内容:
高级别的数据绑定配置
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 mapper.enable(SerializationFeature.INDENT_OUTPUT); mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT); mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false )  mapper.setSerializationInclusion(Include.NON_NULL);  mapper.setDefaultPropertyInclusion(Include.NON_DEFAULT); mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false ); mapper.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, false ); 
 
低级别的 JSON 解析、生成细节配置
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true ); mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true ); mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true ); mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true ); mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true ); mapper.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true ); 
 
在 Jackson 2.5 版本以后,新增了 enable() 和 disable() 方法可以直接启用或禁用相应的 Feature,推荐使用这种方式。 
在 Jackson 2.13 版本以后,ObjectMapper 中的 enable()、disable() 和 configure() 都被标记为过时,并且这些方法将在 Jackson 3.0 中被移除,推荐使用 JsonMapper 中的同名方法。
如果想要查看全部的 Features,可以查看 Jackson Features 
4.3 常用的 Annotations 
Jackson 会根据其默认方式对 Java 对象进行序列化和反序列化,如果需要,可以灵活的调整它的默认方式,可以使用 Jackson 的注解。常用的注解及用法如下:
注解 
用法 
 
 
@JsonProperty 
作用于属性,把属性的名称序列化时转换为另外一个名称。 
 
@JsonFormat 
作用于属性或者方法,让属性的格式在序列化时转换成指定的格式。 
 
@JsonPropertyOrder 
作用于类, 指定属性在序列化时 JSON 中的顺序。 
 
@JsonCreator 
作用于构造方法,和 @JsonProperty 配合使用,适用有参数的构造方法。 
 
@JsonAnySetter 
作用于属性或者方法,设置未反序列化的属性名和值作为键值存储到 Map 中。 
 
@JsonAnyGetter 
作用于方法 ,获取所有未序列化的属性。 
 
 
@JsonFormat 注解很常用,尤其是在前后端分离的情况下(前后端分离下都是传递 JSON 数据),一般使用它来格式化日期格式与设置时区:
1 2 @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd") private  Date birth;
 
在使用 @JsonFormat 注解后,当前端提交类似 yyyy-MM-dd 的字符串时,Jackson 能够将其反序列化为 Date 类型的数据。注意,如果提交的是字符串类型的时间戳,则会反序列化失败,但数值类型的时间戳能够序列化成功。
改变属性名
 
给 Employee 实体的属性添加 @JsonProperty 注解:
1 2 3 4 5 6 7 8 9 10 11 12 @Getter @Setter @NoArgsConstructor @AllArgsConstructor static  class  Employee  {    @JsonProperty(value = "employeeName", index = 2)      private  String name;     @JsonProperty(index = 1)      private  int  age;     @JsonProperty(index = 0)      private  double  height; } 
 
@JsonProperty 注解的 value 属性用于指定生成的 JSON 字符串字段名,如果不设置,字段名与属性名相同。index 属性可以指定生成的 JSON 字符串字段的顺序,从 0 开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Test @SneakyThrows public  void  testAnnotation ()  {    ObjectMapper  mapper  =  new  ObjectMapper ();     Employee  employee  =  new  Employee ("mofan" , 19 , 178.2 );          LinkedHashMap<String, Object> map = mapper.convertValue(employee, new  TypeReference <>() {     });          Map<String, Object> expectMap = new  LinkedHashMap <>();     expectMap.put("height" , 178.2 );     expectMap.put("age" , 19 );     expectMap.put("employeeName" , "mofan" );     assertThat(map).containsExactlyEntriesOf(expectMap);          String  str  =  mapper.writerWithDefaultPrettyPrinter().writeValueAsString(map);     Employee  readValue  =  mapper.readValue(str, Employee.class);     assertThat(readValue).extracting(Employee::getName, Employee::getAge, Employee::getHeight)             .containsExactly("mofan" , 19 , 178.2 );          String  userJson  =  """              {               "employeeName": "默烦",               "age": 20,               "height": 177.5             }             """ ;    Employee  value  =  mapper.readValue(userJson, Employee.class);     assertThat(value).extracting(Employee::getName, Employee::getAge, Employee::getHeight)             .containsExactly("默烦" , 20 , 177.5 ); } 
 
属性的忽略
 
如果想管理在映射 JSON 的时候包含或排除某些属性,可以用到以下几个注解:
@JsonIgnore  作用于属性、方法、构造方法和注解类,用于排除某个属性,这样该属性就不会被 Jackson 序列化和反序列化。 
@JsonIgnoreProperties 是类注解。在序列化为 JSON 的时候,@JsonIgnoreProperties({"prop1", "prop2"})会忽略 pro1 和 pro2 两个属性。在从 JSON 反序列化为 Java 实体类的时候,@JsonIgnoreProperties(ignoreUnknown=true) 会忽略所有没有 Getter 和 Setter 的属性。该注解在 Java 类和 JSON 不完全匹配的时候很有用。 
@JsonIgnoreType 也是类注解,被注解的类作为别的类的属性时忽略序列化与反序列化。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Test @SneakyThrows public  void  testJsonIgnoreProperties ()  {    JsonIgnorePropertiesObj  obj  =  new  JsonIgnorePropertiesObj ();     obj.setIgnore("ignore" );     obj.setInteger(212 );     obj.setStr("str" );     ObjectMapper  mapper  =  new  ObjectMapper ();     String  json  =  mapper.writeValueAsString(obj);     JsonAssert.with(json).assertNotDefined("ignore" ).assertNotDefined("integer" );     assertThat(mapper.readValue(json, JsonIgnorePropertiesObj.class))         .isNotNull()         .hasAllNullFieldsOrPropertiesExcept("str" ); } @Getter @Setter @JsonIgnoreProperties({"ignore", "integer"}) static  class  JsonIgnorePropertiesObj  {    private  String str;          private  String ignore;          private  Integer integer; } 
 
@JsonIgnore 与 @JsonProperty 的联合使用
 
@JsonIgnore 可以作用于方法上,当其作用于某个属性的 Getter 方法时,这个属性在序列化和反序列化时都会被忽略。
现在有一个需求,一个实体中有一名为 pwd 的属性,该属性表示密码,希望在序列化时忽略这个属性,但反序列化的 JSON 字符串中如果有 pwd 字段,则不被忽略。
很容易想到在 pwd 属性的 Getter 方法上添加 @JsonIgnore 注解,但是这样做是不行的,@JsonIgnore 在匹配字段时,Getter 和 Setter 方法之间是共享的,如果只有一个方法具有@JsonIgnore,另一个方法也会被影响。
可以使用“拆分”注释(一般使用 @JsonProperty)来解决这个问题,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @Test @SneakyThrows public  void  testJsonIgnore ()  {    JsonIgnoreObject  object  =  new  JsonIgnoreObject ();     object.setPwd("123456" );     object.setStr("str" );     object.setInteger(212 );     ObjectMapper  mapper  =  new  ObjectMapper ();     String  json  =  mapper.writeValueAsString(object);          JsonAssert.with(json).assertNotDefined("$.pwd" );     assertThat(mapper.readValue(json, JsonIgnoreObject.class))             .isNotNull()             .extracting(JsonIgnoreObject::getStr, JsonIgnoreObject::getInteger, JsonIgnoreObject::getPwd)             .containsExactly("str" , 212 , null );          String  newJson  =  """              {               "str": "str",               "integer": 212,               "password": "987654321"             }             """ ;    assertThat(mapper.readValue(newJson, JsonIgnoreObject.class))             .extracting(JsonIgnoreObject::getPwd)             .isEqualTo("987654321" ); } @Getter @Setter static  class  JsonIgnoreObject  {    private  String str;     private  Integer integer;     private  String pwd;     @JsonIgnore      public  String getPwd ()  {         return  pwd;     }     @JsonProperty("password")      public  void  setPwd (String pwd)  {         this .pwd = pwd;     } } 
 
可以看到,对 JsonIgnoreObject 对象进行序列化时忽略了 pwd 属性,但反序列化 JSON 字符串时,如果字符串中有 password 字段,它的值会被设置到 JsonIgnoreObject 的 pwd 属性上。
序列化相关注解
 
@JsonPropertyOrder 作用于类,用于指定属性序列化时的顺序。@JsonProperty 的 index 属性也有类似功能,数字越小越靠前。 
@JsonRootName 作用于类,用于指定 JSON 根属性的名称。使用这个注解时,必须启用 SerializationFeature.WRAP_ROOT_VALUE 特性。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @Getter @Setter @NoArgsConstructor @AllArgsConstructor @JsonRootName("root") @JsonPropertyOrder({"decimal", "string", "integer"}) static  class  Root  {    private  String string;     private  Integer integer;     private  Double decimal; } @Test @SneakyThrows public  void  testSerializationAnnotation ()  {         ObjectMapper  mapper  =  new  ObjectMapper ().enable(SerializationFeature.WRAP_ROOT_VALUE);     Root  root  =  new  Root ("str" , 212 , 3.14 );     String  json  =  mapper.writeValueAsString(root);          String  expectJson  =  """              {               "root": {                 "decimal": 3.14,                 "string": "str",                 "integer": 212               }             }             """ ;    JsonAssertions.assertThatJson(json).isEqualTo(expectJson);          LinkedHashMap<String, Object> map = mapper.convertValue(root, new  TypeReference <>() {     });     Map<String, Object> expectMap = new  LinkedHashMap <>();     expectMap.put("decimal" , 3.14 );     expectMap.put("string" , "str" );     expectMap.put("integer" , 212 );     assertThat(map).asInstanceOf(MAP)             .doesNotContainKey("root" )             .hasSize(3 )             .containsExactlyEntriesOf(expectMap); } 
 
自定义构造函数
 
参考链接:java - Cannot construct instance of com.domain.User (no Creators, like default constructor, exist): cannot deserialize from Object value - Stack Overflow 
在反序列化时,Jackson 会 默认  使用对象的无参构造方法和对应的 Setter 方法。如果 只  定义了有参构造方法,没有显式声明无参构造方法,则会在反序列化时抛出 MismatchedInputException 异常,提示没有可用的无参构造方法。
如果想要使用非无参构造方法进行反序列化,可以使用 @JsonCreator 注解指定反序列化时使用的构造方法或者静态工厂方法。
比如要求构造的对象是不可变的,因此在对象所在的类中 只  提供了全参构造方法,此时就可以使用 @JsonCreator 注解。
注意:  @JsonCreator 只能作用在静态方法或构造方法,不能在实例方法上使用,因为那时还没构造出对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @FieldNameConstants record  Student (String stuName, int  age, double  height, JacksonBasicTest.Student.School school)  {    @JsonCreator      Student(@JsonProperty(Fields.stuName)  String stuName,             @JsonProperty(Fields.age)  int  age,             @JsonProperty(Fields.height)  double  height,             @JsonProperty(Fields.school)  School school) {         this .stuName = stuName;         this .age = age;         this .height = height;         this .school = school;     }     @FieldNameConstants      record  School (String name)  {         @JsonCreator          School(@JsonProperty(Fields.name)  String name) {             this .name = name;         }     } } 
 
类中的字段都是不可变的,没有提供无参构造方法,但是有 @JsonCreator 注解的有参构造方法。
注意:  @JsonCreator 需要和 @JsonProperty 注解搭配使用,且 @JsonProperty 注解必须指定 value 属性值。@JsonProperty 注解搭配 Lombok 的 @FieldNameConstants 注解一起“食用”更佳。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test @SneakyThrows public  void  testJsonCreator ()  {    ObjectMapper  mapper  =  new  ObjectMapper ();     String  json  =  """              {               "stuName": "默烦",               "age": 20,               "height": 177.5,               "school": {                 "name": "Test"               }             }             """ ;    Student  student  =  mapper.readValue(json, Student.class);     assertThat(student).extracting(             Student::stuName, Student::age,             Student::height, i -> i.school().name()     ).containsExactly("默烦" , 20 , 177.5 , "Test" ); } 
 
另外,类似的还有一个 @java.beans.ConstructorProperties 注解(由 JDK 提供),不过只能用在构造方法里,但该注解可以单独使用,无需搭配 @JsonProperty 注解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Test @SneakyThrows public  void  testConstructorProperties ()  {    ObjectMapper  mapper  =  new  ObjectMapper ();     String  json  =  """              {               "name": "mofan",               "personAge": 20             }             """ ;    Person  person  =  mapper.readValue(json, Person.class);     assertThat(person).extracting(Person::getPersonName, Person::getPersonAge)             .containsExactly("mofan" , 20 ); } @Getter static  class  Person  {    private  final  String personName;     private  final  int  personAge;     @ConstructorProperties({"name", "personAge"})      public  Person (String name, int  personAge)  {         this .personName = name;         this .personAge = personAge;     } } 
 
@ConstructorProperties 注解的指定的字符串数组与构造方法参数列表一一对应。
比如,@ConstructorProperties({"name", "personAge"}) 中的 name 表示将 JSON 中的 name 属性值反序列化为 Person 构造方法中的第一个参数值。
修改构造方法为如下形式,运行测试方法也能得到相同的结果:
1 2 3 4 5 @ConstructorProperties({"name", "personAge"}) public  Person (String personName, int  personAge)  {    this .personName = personName;     this .personAge = personAge; } 
 
5. 数据类型转换 
5.1 类型转换 
Jackson 有一项很流弊(但并不出名)的功能,就是能够进行任意的 POJO 到 POJO 的转换,其底层实现逻辑是现将原对象转换成 JSON 信息,然后再将 JSON 信息绑定到另一个对象上。
这样的转换有两个步骤:
将 POJO 写为 JSON 
将 JSON 绑定到另一种 POJO 中。 
 
只需:
1 ResultType  result  =  mapper.convertValue(sourceObject, ResultType.class);
 
尝试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Test @SneakyThrows public  void  testConversions ()  {    ObjectMapper  mapper  =  new  ObjectMapper ();          List<Integer> list = new  ArrayList <>();     list.add(1 );     list.add(2 );     list.add(3 );     assertThat(list).containsExactly(1 , 2 , 3 );     int [] ints = mapper.convertValue(list, int [].class);     assertThat(ints).containsExactly(1 , 2 , 3 );          User  user  =  new  User ("mofan" , 19 , 178.3 );     Map<String, Object> map = mapper.convertValue(user, new  TypeReference <>() {     });     assertThat(map).containsExactlyInAnyOrderEntriesOf(Map.of(             "age" , 19 ,             "name" , "mofan" ,             "height" , 178.3      ));          User  pojo  =  mapper.convertValue(map, User.class);     assertThat(pojo).extracting(User::getName, User::getAge, User::getHeight)             .containsExactly("mofan" , 19 , 178.3 );          String  base64  =  "bW9mYW4=" ;     byte [] bytes = mapper.convertValue(base64, byte [].class);     String  decode  =  new  String (bytes, StandardCharsets.UTF_8);     assertThat(decode).isEqualTo("mofan" );          User  people  =  new  User ("默烦" , 19 , 177.5 );     JsonNode  jsonNode  =  mapper.convertValue(people, JsonNode.class);     String  expectJson  =  """              {               "name": "默烦",               "age": 19,               "height": 177.5             }             """ ;    JsonAssertions.assertThatJson(mapper.writeValueAsString(jsonNode)).isEqualTo(expectJson); } 
 
🐮 大了,有没有?
甚至还能解码 Base64,如果不知道什么是 Base64,可以在本站搜索【Base64 编码的那些事】查看。
5.2 解析为 Long 类型 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Test @SneakyThrows public  void  testLongValue ()  {    String  withLong  =  """              {               "id1": "98765432112345",               "id2": "98765432112345L",               "id3": 98765432112345,               "id4": "1001",               "id5": "1001L",               "id6": 1001             }             """ ;    ObjectMapper  mapper  =  new  ObjectMapper ();     ObjectNode  objectNode  =  ((ObjectNode) mapper.readTree(withLong));     JsonNode  id1  =  objectNode.get("id1" );     assertThat(id1.isLong()).isFalse();     JsonNode  id2  =  objectNode.get("id2" );     assertThat(id2.isLong()).isFalse();     JsonNode  id3  =  objectNode.get("id3" );     assertThat(id3.isLong()).isTrue();     JsonNode  id4  =  objectNode.get("id4" );     assertThat(id4.isLong()).isFalse();     JsonNode  id5  =  objectNode.get("id5" );     assertThat(id5.isLong()).isFalse();     JsonNode  id6  =  objectNode.get("id6" );     assertThat(id6.isLong()).isFalse(); } 
 
针对整型数字转换时,如果数字小于 Integer.MAX_VALUE 就会转换成 Integer,而不是 Long。
6. 建造者模式 
6.1 简单使用 
有时会使用建造者模式来构造对象,那如何使用 Jackson 将给定 JSON 信息反序列化成使用了建造者模式的类对应的对象呢?
需要使用到两个注解:
@JsonDeserialize:使用 builder 指定使用哪个建造者进行反序列化 
@JsonPOJOBuilder:标记具体的建造者。默认情况下,要求建造者的建造方法名为 build,设置值的方法前缀是 with 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @Getter @Builder @JsonDeserialize(builder = Person.Builder.class) @AllArgsConstructor(access = AccessLevel.PRIVATE) static  class  Person  {    private  final  String name;     private  final  Integer age;          @JsonPOJOBuilder      static  class  Builder  {         private  String name;         private  Integer age;         Builder withName (String name)  {             this .name = name;             return  this ;         }         Builder withAge (Integer age)  {             this .age = age;             return  this ;         }         public  Person build ()  {             return  new  Person (name, age);         }     } } private  static  final  String  JSON  =  """         {           "name": "mofan",           "age": 21         }         """ ;@Test @SneakyThrows public  void  testSimplyUse ()  {    Person  person  =  new  ObjectMapper ().readValue(JSON, Person.class);     assertThat(person).extracting(Person::getName, Person::getAge)             .containsExactly("mofan" , 21 ); } 
 
6.2 与 Lombok 结合 
实际开发过程中常常使用 Lombok 的 @Builder 注解来实现建造者模式,此时 Jackson 怎么与其结合呢?
Lombok 提供了 @Jacksonized 注解来处理这种情况,它是 @Builder 和 @SuperBuilder 的附加注解,也就是说 @Jacksonized 必须与 @Builder 或 @SuperBuilder 注解一起使用才有效,@Jacksonized 注解能对 Lombok 生成的建造者类进行一些配置,以供 Jackson 反序列化使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Getter @Jacksonized @Builder(builderMethodName = "hiddenBuilder") @AllArgsConstructor(access = AccessLevel.PRIVATE) static  class  Student  {    @JsonProperty("studentName")      @JsonAlias({"stuName", "stu_name"})      private  final  String name;     private  final  Integer age;     public  static  StudentBuilder builder (String name)  {         return  hiddenBuilder().name(name);     } } private  static  final  String  FIRST_STUDENT_JSON  =  """         {           "studentName": "mofan",           "age": 21         }         """ ;private  static  final  String  SECOND_STUDENT_JSON  =  """         {           "stu_name": "默烦",           "age": 0         }         """ ;@Test @SneakyThrows public  void  testUseWithLombok ()  {    ObjectMapper  mapper  =  new  ObjectMapper ();     assertThat(mapper.readValue(FIRST_STUDENT_JSON, Student.class))             .extracting(Student::getName, Student::getAge)             .containsExactly("mofan" , 21 );          assertThat(mapper.readValue(SECOND_STUDENT_JSON, Student.class))             .extracting(Student::getName, Student::getAge)             .containsExactly("默烦" , 0 ); } 
 
@Jacksonized   做了下列这些事:
配置 Jackson 反序列化使用的建造者类,即自动在原始类上添加 @JsonDeserialize 注解,并指定其 builder 的值为 Lombok 生成的建造者类; 
将原始类上与 Jackson 相关的注解复制到建造者类上,比如 @JsonProperty、@JsonAlias 等注解,以便  Jackson 在使用建造者类时能够识别到它们; 
插入 @JsonPOJOBuilder(withPrefix="") 注解在构造器类上,覆盖默认使用 with 作为设置值方法的前缀的设置。如果配置了 @Builder 注解的 setterPrefix 值或 buildMethodName 值,这些配置也会同步到插入; 
对 @SuperBuilder 注解,将构造器的实现类设置为 private。 
 
额外补充一点:如果 JSON 信息中的字段名与目标对象中的字段名不一样,@JsonProperty 注解应该标记在建造者类的字段上,而不是原始类的字段上,除此之外,使用 @JsonAlias 注解设置字段别名时,该字段必须已经被 @JsonProperty 标记。
7. 不仅仅是 JSON 
Jackson 除了用于处理 JSON 外,还提供了一系列拓展用来处理不同类型的配置文件,比如 YAML、XML、BSON、Properties、TOML 等等,使用时需要引入额外的依赖,本节以最常见的 YAML 与 XML 为例。
7.1 处理 YAML 
YAML 是 JSON 的超集,Jackson 既然能够解析 JSON,那么 YAML 也自然不在话下。
1 2 3 4 5 <dependency >     <groupId > com.fasterxml.jackson.dataformat</groupId >      <artifactId > jackson-dataformat-yaml</artifactId >      <version > ${jackson.version}</version >  </dependency > 
 
GitHub 仓库 README:FasterXML/jackson-dataformats-text 
处理方式与处理 JSON 类似,只不过使用的不再是 ObjectMapper,而是其子类 YAMLMapper(其实处理 JSON 使用的也是 ObjectMapper 的子类,名为 JsonMapper)。
比如:
1 2 3 4 5 6 ObjectMapper  mapper  =  new  YAMLMapper ();ObjectMapper  mapper  =  new  ObjectMapper (new  YAMLFactory ());ObjectMapper  mapper  =  YAMLMapper.builder().build();
 
写入与读取
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private  static  final  File  YAML_FILE  =  new  File ("test.yaml" );@Getter @Setter static  class  YamlObject  {    private  String str;     private  Integer integer; } @Test @Order(1) @SneakyThrows public  void  testWrite ()  {    ObjectMapper  mapper  =  new  YAMLMapper ();     YamlObject  object  =  new  YamlObject ();     object.setStr("string" );     object.setInteger(212 );          mapper.writeValue(YAML_FILE, object);     assertThat(YAML_FILE).isNotEmpty(); } @Test @Order(2) @SneakyThrows public  void  testRead ()  {    ObjectMapper  mapper  =  new  ObjectMapper (new  YAMLFactory ());     YamlObject  value  =  mapper.readValue(YAML_FILE, YamlObject.class);     assertThat(value).extracting(YamlObject::getStr, YamlObject::getInteger)             .containsExactly("string" , 212 ); } 
 
7.2 处理 XML 
处理 XML 也需要先导入 Jackson 拓展依赖,同时选择 Woodstox 作为 XML 类库,而不是 JDK 自身提供的实现:
1 2 3 4 5 6 7 8 9 10 11 12 <dependency >     <groupId > com.fasterxml.jackson.dataformat</groupId >      <artifactId > jackson-dataformat-xml</artifactId >      <version > ${jackson.version}</version >  </dependency > <dependency >     <groupId > com.fasterxml.woodstox</groupId >      <artifactId > woodstox-core</artifactId >      <version > 6.5.1</version >  </dependency > 
 
GitHub 仓库:FasterXML/jackson-dataformat-xml 
处理 XML 使用的也是 ObjectMapper 的子类,名为 XmlMapper,构造 XmlMapper 的方式也有很多,但更推荐以下更简单易用的方式:
1 2 3 4 ObjectMapper  mapper  =  new  XmlMapper ();ObjectMapper  mapper  =  XmlMapper.builder().build();
 
至于 new ObjectMapper(new XmlFactory()) 的创建方式无法做到开箱即用,需要进行额外的配置。
写入与读取
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private  static  final  File  XML_FILE  =  new  File ("test.xml" );@Getter @Setter static  class  XmlObject  {    private  String string;     private  Integer integer; } @Test @SneakyThrows public  void  testWrite ()  {    ObjectMapper  mapper  =  new  XmlMapper ();     XmlObject  object  =  new  XmlObject ();     object.setString("string" );     object.setInteger(212 );     mapper.writeValue(XML_FILE, object);     assertThat(XML_FILE).isNotEmpty(); } @Test @SneakyThrows public  void  testRead ()  {    ObjectMapper  mapper  =  XmlMapper.builder().build();     XmlObject  value  =  mapper.readValue(XML_FILE, XmlObject.class);     assertThat(value).extracting(XmlObject::getString, XmlObject::getInteger)             .containsExactly("string" , 212 ); } 
 
test.xml 的文件内容如下:
1 <XmlObject > <string > string</string > <integer > 212</integer > </XmlObject > 
 
相关的几个注解
 
@JacksonXmlRootElement:namespace 属性用于指定 XML 标签中 xmlns 的值,显式设置 namespace 的值后,其内部子标签都将增加 xmlns 属性。localName 属性指定 XML 标签的名称。
 
@JacksonXmlProperty:namespace 属性用于指定 XML 标签中 xmlns 的值。localName 属性指定 XML 标签的名称。isAttribute 指定当前字段是否作为父标签的属性,默认为 false,表示作为父标签的子标签。
 
@JacksonXmlText:将字段值直接作为普通文本。
 
@JacksonXmlCData:将字段值包裹在 CDATA 标签中。
 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Getter @Setter @JacksonXmlRootElement(localName = "Root") static  class  AnnotationTestObject  {    private  String string;     private  Integer integer;     private  Double decimal;     @JacksonXmlText      private  String str;     @JacksonXmlCData      private  String code;     @JacksonXmlProperty(isAttribute = true)      @JsonFormat(pattern = "yyyy-MM-dd")      private  Date date; } @Test @SneakyThrows public  void  testAnnotation ()  {    AnnotationTestObject  object  =  new  AnnotationTestObject ();     object.setString("string" );     object.setInteger(212 );     object.setDecimal(3.14 );     object.setStr("str" );     object.setCode("hello world!" );     object.setDate(new  Date (1672502400000L ));     XmlMapper  mapper  =  XmlMapper.builder()                          .enable(SerializationFeature.INDENT_OUTPUT)             .build();     File  file  =  new  File ("xml-annotation.xml" );     mapper.writeValue(file, object);     assertThat(file).isNotEmpty(); } 
 
xml-annotation.xml 的文件内容如下:
1 2 3 4 5 6 <Root  date ="2022-12-31" >   <string > string</string >    <integer > 212</integer >    <decimal > 3.14</decimal > str   <code > <![CDATA[hello world!]]></code >  </Root > 
 
8. 更多注解 
注解 
含义 
 
 
@JacksonAnnotation 
Jackson 注解的元注解,用于标记当前注解是 Jackson 注解的一部分 
 
@JacksonAnnotationsInside 
将多个 Jackson 注解组合成一个自定义注解 
 
 
9. 杂项补充 
9.1 JsonAutoDetect 
默认情况下,Jackson 使用 public 的字段、public 的 Getter/Setter 进行序列化或反序列化。
这种可见性规则是可以显示指定的,比如在第三方类库中很可能存在不符合默认序列化要求的类,但又想让其能够被序列化或反序列化,此时可以显示指定 Jackson 的 JsonAutoDetect 可见性规则。
1 2 3 4 5 6 7 8 9 10 11 static  class  Student  {    private  String name;     private  Integer age;     public  static  Student of (String name, Integer age)  {         Student  student  =  new  Student ();         student.name = name;         student.age = age;         return  student;     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Test @SneakyThrows public  void  testJsonAutoDetect ()  {    ObjectMapper  mapper  =  new  ObjectMapper ();          mapper.setVisibility(             mapper.getVisibilityChecker()                                          .withFieldVisibility(JsonAutoDetect.Visibility.ANY)     );     Student  student  =  Student.of("默烦" , 21 );     String  json  =  mapper.writeValueAsString(student);     JsonAssert.with(json)             .assertEquals("name" , "默烦" )             .assertEquals("age" , 21 );          assertThat(mapper.readValue(json, Student.class))             .extracting("name" , "age" )             .containsExactly("默烦" , 21 ); } 
 
9.2 属性忽略 
在序列化或反序列化过程中经常会遇到忽略某种或某个属性的情况,在此对这种情况的实现进行一个总结。
只序列化不为 null 的属性
 
1 mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 
 
反序列化时忽略在 JSON 中存在、而在 Java 类中不存在的属性
 
1 mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); 
 
利用注解简单实现属性忽略
 
@JsonIgnore:使被标记的字段或属性不会被序列化、反序列化 
@JsonIgnoreProperties:使指定的多个属性不会被序列化、反序列化 
@JsonIgnoreType:使某种类型不会被序列化或反序列化 
 
使用方式可以参考:JsonIgnoreTest.java 
利用 @JsonFilter 实现序列化或反序列化的单一属性忽略
 
某些情况下可能只要求在序列化(或反序列化)时进行属性忽略,而在反序列化(或序列化)时不进行属性忽略,此时可以使用 @JsonFilter 指定自定义的过滤器。
比如要求不序列化 Person 的 idCardNum 字段、Person 中 company 字段的 location 字段,需要先使用 @JsonFilter 注解指定过滤器名称:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Getter @Setter @JsonFilter("idCardNumFilter") static  class  Person  {    private  String name;     private  Integer age;     private  String idCardNum;     @JsonIgnore      private  String otherSensitiveInfo;     @JsonFilter("locationFilter")      private  Company company; } @Getter @Setter static  class  Company  {    private  String name;     private  String location; } 
 
对于指定的过滤器名称,要求在 ObjectMapper 对象中一定要存在同名的过滤器,否则运行时抛出 JsonMappingException 异常,提示没有配置指定名称的过滤器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @Test @SneakyThrows public  void  testJsonFilter ()  {    SimpleFilterProvider  filterProvider  =  new  SimpleFilterProvider ();          filterProvider.addFilter("idCardNumFilter" , SimpleBeanPropertyFilter.serializeAllExcept("idCardNum" ))             .addFilter("locationFilter" , SimpleBeanPropertyFilter.serializeAllExcept("location" ));     ObjectMapper  mapper  =  new  ObjectMapper ();     mapper.setFilterProvider(filterProvider);     Person  person  =  new  Person ();     person.setName("默烦" );     person.setAge(21 );     person.setIdCardNum("XXX" );     person.setOtherSensitiveInfo("ABC" );     Company  company  =  new  Company ();     company.setName("百年不倒股份有限公司" );     company.setLocation("Have a guess" );     person.setCompany(company);     String  json  =  mapper.writeValueAsString(person);     JsonAssert.with(json).assertNotDefined("idCardNum" )             .assertNotDefined("otherSensitiveInfo" )             .assertNotDefined("$.company.location" )             .assertThat("$.name" , equalTo("默烦" ))             .assertThat("$.age" , equalTo(21 ));          String  deserializedJson  =  """              {               "name": "默烦",               "age": 21,               "idCardNum": "XXX",               "otherSensitiveInfo": "ABC",               "company": {                 "name": "百年不倒股份有限公司",                 "location": "UNKNOWN"               }             }             """ ;    Person  newPerson  =  mapper.readValue(deserializedJson, Person.class);     assertThat(newPerson).hasNoNullFieldsOrPropertiesExcept("otherSensitiveInfo" )             .extracting("idCardNum" , "company.location" )             .containsExactly("XXX" , "UNKNOWN" ); } 
 
直接在目标类上添加注解来实现属性忽略将作用于全局,而在某些极端场景下要求只在该场景进行属性忽略,此时可以使用 Jackson 的 Mix-in 特性来实现。
1 2 3 4 5 6 7 8 @Getter @Setter static  class  SimpleObj  {    private  String a;     private  String b;     private  String c;     private  String d; } 
 
比如只要求不序列化 c、d 字段,对 a、b 字段依旧照常序列化,那么可以先定义 Mix-in 类,在该类上使用 @JsonFilter 指定过滤器,而后使 MixIn 类“混入”目标类 SimpleObj 以实现这种极端场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @JsonFilter("selectiveFilter") static  class  SelectiveFilterMixIn  {} @Test @SneakyThrows public  void  testSelectiveFilter ()  {    SimpleObj  obj  =  new  SimpleObj ();     obj.setA("a" );     obj.setB("b" );     obj.setC("c" );     obj.setD("d" );     ObjectMapper  mapper  =  new  ObjectMapper ();     SimpleFilterProvider  provider  =  new  SimpleFilterProvider ();     provider.addFilter("selectiveFilter" , SimpleBeanPropertyFilter.serializeAllExcept("c" , "d" ));     mapper.setFilterProvider(provider)         .addMixIn(SimpleObj.class, SelectiveFilterMixIn.class);     JsonAssert.with(mapper.writeValueAsString(obj))         .assertNotDefined("c" )         .assertNotDefined("d" )         .assertEquals("a" , "a" )         .assertEquals("b" , "b" ); } 
 
9.3 Mix-in 
在【8.2 属性忽略】中首次接触到了 Mix-in,Mix-in 可直译为“混入”,是一种将注解与类向关联的方式,而无需修改目标类本身,旨在帮助用户将某些 Jackson 的注解添加到无法修改源码的第三方类库中的类上。
可供参考的官方链接:JacksonMixInAnnotations · FasterXML/jackson-docs Wiki 
序列化第三方类库中的类
 
除了修改 Jackson 的 JsonAutoDetect 可见性规则来使第三方类库中的类可被序列化之外,还可以使用 Mix-in 实现。
假设有一第三方类库中的类为 Person,所有字段均为 private,并且没有提供任何公共的 Getter/Setter,也没有无参构造,显然无法使用 Jackson 的默认序列化规则对其进行序列化或反序列化:
1 2 3 4 5 6 7 8 9 10 static  class  Person  {    private  final  String firstName;     private  final  String lastName;     public  Person (String firstName, String lastName)  {         this .firstName = firstName;         this .lastName = lastName;     } } 
 
创建 Person 的 Mix-in 类,使它具有与 Person 相同的字段,使用合适的注解指定其被序列化或反序列化使用的信息:
1 2 3 4 5 6 7 8 9 10 11 12 abstract  static  class  PersonMixIn  {    @JsonProperty      private  final  String firstName;     @JsonProperty      private  final  String lastName;     @JsonCreator      public  PersonMixIn (@JsonProperty("firstName")  String firstName, @JsonProperty("lastName")  String lastName)  {         this .firstName = firstName;         this .lastName = lastName;     } } 
 
接下来关联 Person 类与其 Mix-in 类,使得 Person 能够被序列化或反序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test @SneakyThrows public  void  testSerializeThirdPartyObject ()  {    Person  person  =  new  Person ("默" , "烦" );     ObjectMapper  mapper  =  new  ObjectMapper ();     assertThatThrownBy(() -> mapper.writeValueAsString(person))             .isInstanceOf(InvalidDefinitionException.class);          ObjectMapper  anotherMapper  =  new  ObjectMapper ();     anotherMapper.addMixIn(Person.class, PersonMixIn.class);     String  json  =  anotherMapper.writeValueAsString(person);     JsonAssert.with(json).assertThat("$.firstName" , equalTo("默" ))             .assertThat("$.lastName" , equalTo("烦" ));     Person  newPerson  =  anotherMapper.readValue(json, Person.class);     assertThat(newPerson).extracting("firstName" , "lastName" )             .containsExactly("默" , "烦" ); } 
 
序列化时忽略、改变第三方类库中类的属性
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Getter @Setter static  class  Student  {    private  String name;     private  String idCardNum; } abstract  static  class  StudentMixIn  {    @JsonProperty("NAME")      private  String name;     @JsonIgnore      private  String idCardNum; } @Test @SneakyThrows public  void  testIgnoreAndChangeThirdPartyObjectProperties ()  {    Student  student  =  new  Student ();     student.setName("默烦" );     student.setIdCardNum("xxx" );     ObjectMapper  mapper  =  new  ObjectMapper ();     mapper.addMixIn(Student.class, StudentMixIn.class);     String  json  =  mapper.writeValueAsString(student);     JsonAssert.with(json).assertNotDefined("idCardNum" )             .assertThat("$.NAME" , equalTo("默烦" ));     Student  newStu  =  mapper.readValue(json, Student.class);     assertThat(newStu).extracting("name" ).isEqualTo("默烦" ); } 
 
修改自定义序列化器、反序列化器
 
某些情况下要求某个类在序列化或反序列化时不使用其默认的序列化器或反序列化器,这种需求也可以使用 Mix-in 实现。
比如 People 默认的序列化器是 PeopleSerializer,默认的反序列化器是 PeopleDeserializer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 private  static  final  String  SEPARATOR  =  "->" ;@Getter @EqualsAndHashCode(of = {"realName", "nickName"}) @JsonSerialize(using = People.PeopleSerializer.class) @JsonDeserialize(using = People.PeopleDeserializer.class) static  class  People  {    private  final  String realName;     private  final  String nickName;     @JsonCreator      public  People (@JsonProperty  String realName, @JsonProperty  String nickName)  {         this .realName = realName;         this .nickName = nickName;     }     static  class  PeopleSerializer  extends  JsonSerializer <People> {         @Override          public  void  serialize (People people, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)  throws  IOException {                          jsonGenerator.writeString(people.getRealName() + SEPARATOR + people.getNickName());         }     }     static  class  PeopleDeserializer  extends  JsonDeserializer <People> {         @Override          public  People deserialize (JsonParser jsonParser, DeserializationContext deserializationContext)  throws  IOException, JacksonException {             String[] split = jsonParser.getValueAsString().split(SEPARATOR);             return  new  People (split[0 ], split[1 ]);         }     } } 
 
现在要求修改这种默认配置,而使用用户自定义的配置,同样是创建对应的 Mix-in 类(或接口),然后添加自定义配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @JsonSerialize(using = PeopleMixIn.AnotherPeopleSerializer.class) @JsonDeserialize(using = PeopleMixIn.AnotherPeopleDeserializer.class) static  abstract  class  PeopleMixIn  {    static  class  AnotherPeopleSerializer  extends  JsonSerializer <People> {         @Override          public  void  serialize (People people, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)  throws  IOException {                          jsonGenerator.writeString(people.getNickName() + SEPARATOR + people.getRealName());         }     }     static  class  AnotherPeopleDeserializer  extends  JsonDeserializer <People> {         @Override          public  People deserialize (JsonParser jsonParser, DeserializationContext deserializationContext)  throws  IOException, JacksonException {             String[] split = jsonParser.getValueAsString().split(SEPARATOR);             return  new  People (split[1 ], split[0 ]);         }     } } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test @SneakyThrows public  void  testChangeCustomSerializer ()  {    People  people  =  new  People ("默烦" , "mofan212" );     ObjectMapper  mapper  =  new  ObjectMapper ();     String  str  =  mapper.writeValueAsString(people);     assertThat(str).isEqualTo("\"默烦->mofan212\"" );     People  newPeople  =  mapper.readValue(str, People.class);     assertThat(newPeople).isEqualTo(people);     mapper = new  ObjectMapper ();     mapper.addMixIn(People.class, PeopleMixIn.class);     str = mapper.writeValueAsString(people);     assertThat(str).isEqualTo("\"mofan212->默烦\"" );     newPeople = mapper.readValue(str, People.class);     assertThat(newPeople).isEqualTo(people); } 
 
9.4 序列化中的循环引用 
当 A 对象依赖 B 对象,B 对象有依赖 A 对象,且相同对象都指向同一个内存地址时,此时对 A 对象进行序列化就会产生 StackOverflowError 错误,要想解决这个问题,就需要切断对象间的循环引用,可以使用这几个方式:
序列化时忽略相互引用的字段 
使用 @JsonIdentityInfo 注解序列化循环引用的标识符,而不是序列化完整对象 
使用 @JsonBackReference 注解指定引用关系 
 
忽略相互引用的字段
 
使用 @JsonIgnoreProperties 注解忽略相互引用的字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 @Getter @Setter static  class  User  {    private  String username;     private  String pwd;     private  Role role; } @Getter @Setter static  class  Role  {    private  String name;     @JsonIgnoreProperties("role")      private  List<User> users; } @Test @SneakyThrows public  void  testCircularReference ()  {    User  user  =  new  User ();     user.setUsername("mofan" );     user.setPwd("123456" );     Role  role  =  new  Role ();     role.setName("admin" );     role.setUsers(List.of(user));     user.setRole(role);     JsonMapper  mapper  =  JsonMapper.builder().build();     String  json  =  mapper.writeValueAsString(user);          String  expectJson  =  """              {               "username": "mofan",               "pwd": "123456",               "role": {                 "name": "admin",                 "users": [                   {                     "username": "mofan",                     "pwd": "123456"                   }                 ]               }             }             """ ;    JsonAssertions.assertThatJson(json).isEqualTo(expectJson); } 
 
这样做尽管解决了循环引用,但再次对序列化得到的 JSON 信息进行反序列化时,会造成信息丢失。
使用 @JsonIdentityInfo 注解
 
使用 @JsonIdentityInfo 注解后,在序列化过程中遇到相同引用的对象时,将使用指定的标识符代替该对象,而不是将该对象完整地序列化。
一对一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 @Getter @Setter @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") static  class  Customer  {    private  Long id;     private  String name;     private  Order order; } @Getter @Setter static  class  Order  {    private  Long orderId;     private  List<Integer> itemIds;     private  Customer customer; } @Test @SneakyThrows public  void  testSimplyUse ()  {    Order  order  =  new  Order ();     order.setOrderId(1L );     order.setItemIds(List.of(10 , 30 ));     Customer  customer  =  new  Customer ();     customer.setId(2L );     customer.setName("Peter" );          customer.setOrder(order);          order.setCustomer(customer);     JsonMapper  mapper  =  JsonMapper.builder().build();          String  json  =  mapper.writeValueAsString(customer);          String  expectJson  =  """              {               "id": 2,               "name": "Peter",               "order": {                 "orderId": 1,                 "itemIds": [10, 30],                 "customer": 2               }             }              """ ;         JsonAssertions.assertThatJson(json).isEqualTo(expectJson);     Customer  newCustom  =  mapper.readValue(json, Customer.class);     Assertions.assertThat(newCustom).isEqualTo(newCustom.getOrder().getCustomer()).extracting(             Customer::getId, Customer::getName,             i -> i.getOrder().getOrderId(), i -> i.getOrder().getItemIds()     ).containsSequence(2L , "Peter" , 1L , List.of(10 , 30 ));     Assertions.assertThat(newCustom).extracting(i -> i.getOrder().getCustomer())             .extracting(Customer::getId, Customer::getName)             .containsSequence(2L , "Peter" ); } 
 
 
一对多:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 @Getter @Setter @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "empId") static  class  Employee  {    private  Long empId;     private  List<Dept> depts; } @Getter @Setter static  class  Dept  {    private  Long deptId;     private  List<Employee> employees; } @Test @SneakyThrows public  void  testOne2Many ()  {    Employee  employee  =  new  Employee ();     employee.setEmpId(1L );     Dept  dept1  =  new  Dept ();     dept1.setDeptId(11L );     dept1.setEmployees(Collections.singletonList(employee));     Dept  dept2  =  new  Dept ();     dept2.setDeptId(12L );     dept2.setEmployees(Collections.singletonList(employee));     employee.setDepts(List.of(dept1, dept2));     JsonMapper  mapper  =  JsonMapper.builder().build();     String  json  =  mapper.writeValueAsString(employee);          String  expectJson  =  """              {               "empId": 1,               "depts": [                 {                   "deptId": 11,                   "employees": [1]                 },                 {                   "deptId": 12,                   "employees": [1]                 }               ]             }             """ ;    JsonAssertions.assertThatJson(json).isEqualTo(expectJson);     Employee  newEmp  =  mapper.readValue(json, Employee.class);     Assertions.assertThat(newEmp).extracting(             i -> i.getDepts().get(0 ).getEmployees().get(0 ).getEmpId(),             i -> i.getDepts().get(1 ).getEmployees().get(0 ).getEmpId()     ).containsSequence(1L , 1L ); } 
 
 
 
@JsonIdentityInfo 注解中的 generator 除了指定为 PropertyGenerator 外,还有其他生成器可供选择,比如:
StringIdGenerator 
UUIDGenerator 
IntSequenceGenerator 
 
当然也可以自定义生成器。
使用 IntSequenceGenerator 作为生成器处理自引用关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @Getter @Setter @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "@key") static  class  Entity  {    private  String name;     private  List<Entity> entities; } @Test @SneakyThrows public  void  testIntSequenceGenerator ()  {    Entity  entity  =  new  Entity ();     entity.setName("Root" );     Entity  firstSubEntity  =  new  Entity ();     firstSubEntity.setName("SubEntity-1" );     Entity  secondSubEntity  =  new  Entity ();     secondSubEntity.setName("SubEntity-2" );     entity.setEntities(List.of(entity, firstSubEntity, secondSubEntity));     JsonMapper  mapper  =  JsonMapper.builder().build();     String  expectJson  =  """              {               "@key": 1,               "name": "Root",               "entities": [                 1,                 {                   "@key": 2,                   "name": "SubEntity-1",                   "entities": null                 },                 {                   "@key": 3,                   "name": "SubEntity-2",                   "entities": null                 }               ]             }             """ ;    JsonAssertions.assertThatJson(mapper.writeValueAsString(entity))             .isEqualTo(expectJson); } 
 
使用 @JsonBackReference 注解
 
使用 @JsonBackReference 注解后进行序列化时,得到的结果与使用了 @JsonIgnore 类似,而与 @JsonManagedReference 注解联合使用指定引用关系时,对先前序列化的结果进行反序列化后,得到的对象信息并不会缺失。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 @Getter @Setter static  class  Employer  {    private  String name;     @JsonManagedReference      private  List<Employee> employees; } @Getter @Setter static  class  Employee  {    private  String name;     @JsonBackReference      private  Employer employer; } @Test @SneakyThrows public  void  test ()  {    Employee  employee  =  new  Employee ();     employee.setName("mofan" );     Employer  employer  =  new  Employer ();     employer.setName("默烦" );     employer.setEmployees(List.of(employee));     employee.setEmployer(employer);     JsonMapper  mapper  =  JsonMapper.builder().build();     String  json  =  mapper.writeValueAsString(employer);     String  expectJson  =  """              {               "name": "默烦",               "employees": [                 {                   "name": "mofan"                 }               ]             }             """ ;         JsonAssertions.assertThatJson(json).isEqualTo(expectJson);          Employer  value  =  mapper.readValue(expectJson, Employer.class);     assertThat(value.getEmployees()).map(Employee::getEmployer)             .filteredOn(Objects::nonNull)             .hasSize(1 )             .map(Employer::getName)             .singleElement()             .isEqualTo("默烦" ); } 
 
9.5 对 JDK8 日期时间的支持 
Jackson 也对 JDK8 新增的日期时间类提供了支持,这些支持以 Jackson 模块的形式提供,因此需要导入对应的依赖:
1 2 3 4 5 <dependency >     <groupId > com.fasterxml.jackson.datatype</groupId >      <artifactId > jackson-datatype-jsr310</artifactId >      <version > ${jackson.version}</version >  </dependency > 
 
导入依赖后并不是就万事大吉了,还要在使用时注册这些模块。
可以按需单个注册:
1 mapper.addModule(new  JavaTimeModule ()); 
 
也可以让 Jackson 自己查找并注册所有模块:
1 mapper.findAndRegisterModules(); 
 
简单测试下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Getter @Setter static  class  TimeObject  {    @JsonFormat(pattern = "yyyy-MM-dd")      private  LocalDate localDate; } @Test @SneakyThrows public  void  testSupportDateTime ()  {    TimeObject  object  =  new  TimeObject ();     object.setLocalDate(LocalDate.of(2023 , 1 , 1 ));     assertThatThrownBy(() -> new  ObjectMapper ().writeValueAsString(object))             .isExactlyInstanceOf(InvalidDefinitionException.class);     ObjectMapper  mapper  =  JsonMapper.builder()             .addModule(new  JavaTimeModule ())             .build();     String  expectJson  =  """              {               "localDate": "2023-01-01"             }             """ ;    JsonAssertions.assertThatJson(mapper.writeValueAsString(object))             .isEqualTo(expectJson); } 
 
9.6 传播 transient 
Java 提供了 transient 关键字用来修饰无需被序列化的字段,但在默认情况下,Jackson 必不认这个关键字,除非开启 MapperFeature.PROPAGATE_TRANSIENT_MARKER 的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @Getter @Setter public  class  Phone  implements  Serializable  {    @Serial      private  static  final  long  serialVersionUID  =  -2025192839395117683L ;     public  static  String staticStr;     private  String name;     private  transient  BigDecimal price; } @Test @SneakyThrows public  void  testSerializedPhoneByJackson ()  {    Phone  phone  =  new  Phone ();     phone.setName("XiaoMi" );     phone.setPrice(new  BigDecimal ("1999.0" ));     Phone.staticStr = "Hello, Thank you" ;     ObjectMapper  mapper  =  new  ObjectMapper ();     String  json  =  mapper.writeValueAsString(phone);     String  except  =  """              {                 "name": "XiaoMi"             }             """ ;    assertThatJson(json)             .isObject()             .isNotEqualTo(except)             .containsEntry("price" , new  BigDecimal ("1999.0" ))                          .doesNotContainKey("staticStr" );          mapper = JsonMapper.builder().configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true ).build();     json = mapper.writeValueAsString(phone);     assertThatJson(json).isEqualTo(except); } 
 
针对静态字段来说,Jackson 与 Java 的序列化机制一样,也不会序列化静态字段。
10. 使用细节 
10.1 与 Lombok 一起使用可能存在的问题 
存在名为 MyFirstData 的类,使用 Lombok 为其生成 Getter/Setter:
1 2 3 4 5 6 @Getter @Setter static  class  MyFirstData  {    private  String s;     private  Integer iAge; } 
 
将给定字符串反序列化成 MyFirstData 实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 static  JsonMapper  mapper  =  JsonMapper.builder().build();static  final  String  JSON  =  """         {           "iAge": 21         }                     """ ;@Test @SneakyThrows public  void  testDeserializeFirstData ()  {         assertThatExceptionOfType(UnrecognizedPropertyException.class)             .isThrownBy(() -> mapper.readValue(JSON, MyFirstData.class));          JsonMapper  jsonMapper  =  JsonMapper.builder()             .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)             .build();     try  {         MyFirstData  data  =  jsonMapper.readValue(JSON, MyFirstData.class);         assertThat(data).extracting(MyFirstData::getIAge).isNull();     } catch  (JsonProcessingException e) {         throw  new  RuntimeException (e);     }     MyFirstData  data  =  new  MyFirstData ();     data.setIAge(21 );     String  result  =  mapper.writeValueAsString(data);     JsonAssertions.assertThatJson(result).isObject()                          .doesNotContainKey("iAge" )                          .containsEntry("iage" , 21 ); } 
 
第一次反序列化时抛出了异常,提示无法识别 JSON 串中的 iAge 字段,而后加上停用 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES 特性再次反序列化时,不再抛出异常,但 MyFirstData 实例中的 iAge 字段值为 null。
新建一个 MyFirstData 实例,对其进行序列化,在得到的 JSON 中不包含 iAge 的字段,而是包含名为 iage 的字段,这是为什么?
这是因为 Lombok 生成的 Setter 与预期不符,查看编译生成的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static  class  MyFirstData  {    private  String s;     private  Integer iAge;     MyFirstData() {     }     public  String getS ()  {         return  this .s;     }     public  Integer getIAge ()  {         return  this .iAge;     }     public  void  setS (String s)  {         this .s = s;     }     public  void  setIAge (Integer iAge)  {         this .iAge = iAge;     } } 
 
iAge 字段生成的 Set 方法名为 setIAge,Jackson 在初始化序列器时,会使用 DefaultAccessorNamingStrategy#legacyManglePropertyName() 方法来处理 Getter/Setter,它会将方法的 get/set 前缀移除,将其后面连续大写字符转换为小写字符返回。
如果使用 IDEA 生成的 Getter/Setter 就不会出现这样的问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @SuppressWarnings("SpellCheckingInspection") static  class  MySecondData  {    private  Integer iAge;     public  Integer getiAge ()  {         return  iAge;     }     public  void  setiAge (Integer iAge)  {         this .iAge = iAge;     } } @Test public  void  testDeserializeFourthData ()  {    validDeserializeToTargetClass(MyFourthData.class); } @SneakyThrows private  <T> void  validDeserializeToTargetClass (Class<T> clazz)  {    T  data  =  mapper.readValue(JSON, clazz);     assertThat(data).isNotNull()             .extracting("iAge" )             .isEqualTo(21 );     String  result  =  mapper.writeValueAsString(data);     JsonAssertions.assertThatJson(result).isObject().isEqualTo(JSON); } 
 
如果非要使用 Lombok 生成 Getter/Setter,则需要搭配 @JsonProperty 注解使用:
1 2 3 4 5 6 7 8 9 10 11 @Getter @Setter static  class  MyThirdData  {    @JsonProperty("iAge")      private  Integer iAge; } @Test public  void  testDeserializeThirdData ()  {    validDeserializeToTargetClass(MyThirdData.class); } 
 
编译生成的 MyThirdData 类的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static  class  MyThirdData  {    @JsonProperty("iAge")      private  Integer iAge;     MyThirdData() {     }     public  Integer getIAge ()  {         return  this .iAge;     }     @JsonProperty("iAge")      public  void  setIAge (Integer iAge)  {         this .iAge = iAge;     } } 
 
字段 iAge 和 setIAge() 方法上都使用了 @JsonProperty("iAge") 注解。
其实只在 setIAge() 方法上使用 @JsonProperty("iAge") 注解即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Getter static  class  MyFourthData  {    private  Integer iAge;     @JsonProperty("iAge")      public  void  setIAge (Integer iAge)  {         this .iAge = iAge;     } } @Test public  void  testDeserializeFourthData ()  {    validDeserializeToTargetClass(MyFourthData.class); }