封面来源:碧蓝航线 箱庭疗法 活动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); }