目录
JSON 简述
JSON 定制开发对于开发者并不陌生,如今的 WEB 服务、移动应用、定制开发甚至物联网大多都是以 JSON 定制开发作为数据交换的格式。学习 JSON 定制开发格式的操作工具对开发定制开发者来说是必不可少的。定制开发这篇文章将介绍如何使用 Jackson 定制开发开源工具库对 JSON 定制开发进行常见操作
JSON 是 JavaScript Object Notation 的缩写,JSON 定制开发是一种基于文本的格式,定制开发可以把它理解为是一个定制开发结构化的数据,定制开发这个结构化数据中可以定制开发包含键值映射、定制开发嵌套对象以及数组等信息
{ "array": [ 1, 2, 3 ], "boolean": true, "color": "gold", "null": null, "number": 123, "object": { "a": "b", "c": "d" }, "string": "www.wdbyte.com"}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
Jackson 介绍
Jackson 和 FastJson 一样,是一个 Java 定制开发语言编写的,可以进行 JSON 定制开发处理的开源工具库,Jackson 定制开发的使用非常广泛,Spring 定制开发框架默认使用 Jackson 进行 JSON 处理
Jackson 定制开发有三个核心包,分别是 Streaming、Databid、Annotations,定制开发通过这些包可以方便的对 JSON 进行操作
Streaming[1]在jackson-core模块。定制开发定义了一些流处理相关的API以及特定的JSON实现Annotations[2]在jackson-annotations模块,包含了Jackson中的注解Databind[3]在jackson-databind模块, 在Streaming包的基础上实现了数据绑定,依赖于Streaming和Annotations包
得益于 Jackson 高扩展性的设计,有很多常见的文本格式以及工具都有对 Jackson 的相应适配,如 CSV、XML、YAML 等
Jackson 的 Maven 依赖
在使用 Jackson 时,大多数情况下我们只需要添加 jackson-databind 依赖项,就可以使用 Jackson 功能了,它依赖了下面两个包
com.fasterxml.jackson.core:jackson-annotationscom.fasterxml.jackson.core:jackson-core
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version></dependency>- 1
- 2
- 3
- 4
- 5
为了方便这篇文章后续的代码演示,我们同时引入 Junit 进行单元测试和 Lombok 以减少 Get/Set 的代码编写
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.8.2</version> <scope>test</scope></dependency><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version></dependency>- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
ObjectMapper 对象映射器
ObjectMapper 是 Jackson 库中最常用的一个类,使用它可以进行 Java 对象和 JSON 字符串之间快速转换。如果你用过 FastJson,那么 Jackson 中的 ObjectMapper 就如同 FastJson 中的 JSON 类
这个类中有一些常用的方法
readValue()方法可以进行JSON的反序列化操作,比如可以将字符串、文件流、字节流、字节数组等将常见的内容转换成Java对象writeValue()方法可以进行JSON的序列化操作,可以将Java对象转换成JSON字符串
大多数情况下,ObjectMapper 的工作原理是通过 Java Bean 对象的 Get/Set 方法进行转换时映射的,所以正确编写 Java 对象的 Get/Set 方法尤为重要,不过 ObjectMapper 也提供了诸多配置,比如可以通过配置或者注解的形式对 Java 对象和 JSON 字符串之间的转换过程进行自定义。这些在下面部分都会介绍到
Jackson JSON 的基本操作
Jackson 作为一个 Java 中的 JSON 工具库,处理 JSON 字符串和 Java 对象是它最基本最常用的功能,下面通过一些例子来演示其中的用法
Jackson JSON 的序列化
编写一个 Person 类,定义三个属性,名称、年龄以及技能
@Datapublic class Person { private String name; private Integer age; private List<String> skillList;}- 1
- 2
- 3
- 4
- 5
- 6
- 7
将 Java 对象转换成 JSON 字符串
public class PersonTest { ObjectMapper objectMapper = new ObjectMapper(); @Test void pojoToJsonString() throws JsonProcessingException { Person person = new Person(); person.setName("aLng"); person.setAge(27); person.setSkillList(Arrays.asList("java", "c++")); String json = objectMapper.writeValueAsString(person); System.out.println(json); String expectedJson = "{\"name\":\"aLng\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}"; Assertions.assertEquals(json, expectedJson); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
输出的 JSON 字符串
{"name":"aLng","age":27,"skillList":["java","c++"]}- 1
Jackson 甚至可以直接把序列化后的 JSON 字符串写入文件或者读取成字节数组
mapper.writeValue(new File("result.json"), myResultObject);// 或者byte[] jsonBytes = mapper.writeValueAsBytes(myResultObject);// 或者String jsonString = mapper.writeValueAsString(myResultObject);- 1
- 2
- 3
- 4
- 5
Jackson JSON 的
public class PersonTest { ObjectMapper objectMapper = new ObjectMapper(); @Test void jsonStringToPojo() throws JsonProcessingException { String expectedJson = "{\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}"; Person person = objectMapper.readValue(expectedJson, Person.class); System.out.println(person); Assertions.assertEquals(person.getName(), "aLang"); Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]"); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
输出结果
Person(name=aLang, age=27, skillList=[java, c++])- 1
上面的例子演示了如何使用 Jackson 把一个 JSON 字符串反序列化成 Java 对象,其实 Jackson 对文件中的 JSON 字符串、字节形式的 JSON 字符串反序列化同样简单
比如先准备了一个 JSON 内容文件 Person.json
{ "name": "aLang", "age": 27, "skillList": [ "java", "c++" ]}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
下面进行读取转换
ObjectMapper objectMapper = new ObjectMapper();@Testvoid testJsonFilePojo() throws IOException { File file = new File("src/Person.json"); Person person = objectMapper.readValue(file, Person.class); // 或者 // person = mapper.readValue(new URL("http://some.com/api/entry.json"), MyValue.class); System.out.println(person); Assertions.assertEquals(person.getName(), "aLang"); Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]");}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
同样输出了 Person 内容
Person(name=aLang, age=27, skillList=[java, c++])- 1
JSON 转 List
上面演示 JSON 字符串都是单个对象的,如果 JSON 是一个对象列表那么使用 Jackson 该怎么处理呢?
已经存在一个文件 PersonList.json
[ { "name": "aLang", "age": 27, "skillList": [ "java", "c++" ] }, { "name": "darcy", "age": 26, "skillList": [ "go", "rust" ] }]- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
读取它然后转换成 List<Person>
ObjectMapper objectMapper = new ObjectMapper();@Testvoid fileToPojoList() throws IOException { File file = new File("src/EmployeeList.json"); List<Person> personList = objectMapper.readValue(file, new TypeReference<List<Person>>() {}); for (Person person : personList) { System.out.println(person); } Assertions.assertEquals(personList.size(), 2); Assertions.assertEquals(personList.get(0).getName(), "aLang"); Assertions.assertEquals(personList.get(1).getName(), "darcy");}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
可以输出对象内容
Person(name=aLang, age=27, skillList=[java, c++])Person(name=darcy, age=26, skillList=[go, rust])- 1
- 2
JSON 转 Map
JSON 转 Map 在我们没有一个对象的 Java 对象时十分实用,下面演示如何使用 Jackson 把 JSON 文本转成 Map 对象
ObjectMapper objectMapper = new ObjectMapper();@Testvoid jsonStringToMap() throws IOException { String expectedJson = "{\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}"; Map<String, Object> employeeMap = objectMapper.readValue(expectedJson, new TypeReference<Map>() {}); System.out.println(employeeMap.getClass()); for (Entry<String, Object> entry : employeeMap.entrySet()) { System.out.println(entry.getKey() + ":" + entry.getValue()); } Assertions.assertEquals(employeeMap.get("name"), "aLang");}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
可以看到 Map 的输出结果
class java.util.LinkedHashMapname:aLangage:27skillList:[java, c++]- 1
- 2
- 3
- 4
Jackson 的忽略字段
如果在进行 JSON 转 Java 对象时,JSON 中出现了 Java 类中不存在的属性,那么在转换时会遇到 com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException 异常
使用 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 可以忽略不存在的属性
ObjectMapper objectMapper = new ObjectMapper();@Testvoid jsonStringToPojoIgnoreProperties() throws IOException { // UnrecognizedPropertyException String json = "{\"yyy\":\"xxx\",\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}"; objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); Person person = objectMapper.readValue(json, Person.class); System.out.printf(person.toString()); Assertions.assertEquals(person.getName(), "aLang"); Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]");}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
正常输出
Person(name=aLang, age=27, skillList=[java, c++])- 1
Jackson 的
在 Java 8 之前我们通常使用 java.util.Date 类来处理时间,但是在 Java 8 发布时引入了新的时间类 java.time.LocalDateTime. 这两者在 Jackson 中的处理略有不同
先创建一个有两种时间类型属性的 Order 类
@Data@AllArgsConstructor@NoArgsConstructorpublic class Order { private Integer id; private Date createTime; private LocalDateTime updateTime;}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
Date 类型
下面我们新建一个测试用例来测试两种时间类型的 JSON 转换
class OrderTest { ObjectMapper objectMapper = new ObjectMapper(); @Test void testPojoToJson0() throws JsonProcessingException { Order order = new Order(1, new Date(), null); String json = objectMapper.writeValueAsString(order); System.out.println(json); order = objectMapper.readValue(json, Order.class); System.out.println(order.toString()); Assertions.assertEquals(order.getId(), 1); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
在这个测试代码中,我们只初始化了 Date 类型的时间,下面是输出的结果
{"id":1,"createTime":1658320852395,"updateTime":null}Order(id=1, createTime=Wed Jul 20 20:40:52 CST 2022, updateTime=null)- 1
- 2
可以看到正常的进行了 JSON 的序列化与反序列化,但是 JSON 中的时间是一个时间戳格式,可能不是我们想要的
LocalDateTime 类型
为什么没有设置 LocalDateTime 类型的时间呢?因为默认情况下进行 LocalDateTime 类的 JSON 转换会遇到报错
class OrderTest { ObjectMapper objectMapper = new ObjectMapper(); @Test void testPojoToJson() throws JsonProcessingException { Order order = new Order(1, new Date(), LocalDateTime.now()); String json = objectMapper.writeValueAsString(order); System.out.println(json); order = objectMapper.readValue(json, Order.class); System.out.println(order.toString()); Assertions.assertEquals(order.getId(), 1); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
运行后会遇到报错
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: com.wdbyte.jackson.Order["updateTime"])- 1
- 2
- 3
- 4
这里我们需要添加相应的数据绑定支持包
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>2.13.3</version></dependency>- 1
- 2
- 3
- 4
- 5
然后在定义 ObjectMapper 时通过 findAndRegisterModules() 方法来注册依赖
class OrderTest { ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); @Test void testPojoToJson() throws JsonProcessingException { Order order = new Order(1, new Date(), LocalDateTime.now()); String json = objectMapper.writeValueAsString(order); System.out.println(json); order = objectMapper.readValue(json, Order.class); System.out.println(order.toString()); Assertions.assertEquals(order.getId(), 1); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
运行可以得到正常序列化与反序列化日志,不过序列化后的时间格式依旧奇怪
{"id":1,"createTime":1658321191562,"updateTime":[2022,7,20,20,46,31,567000000]}Order(id=1, createTime=Wed Jul 20 20:46:31 CST 2022, updateTime=2022-07-20T20:46:31.567)- 1
- 2
时间格式化
通过在字段上使用注解 @JsonFormat 来自定义时间格式
@Data@AllArgsConstructor@NoArgsConstructorpublic class Order { private Integer id; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private Date createTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime updateTime;}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
再次运行上面的列子可以得到时间格式化后的 JSON 字符串
{"id":1,"createTime":"2022-07-20 20:49:46","updateTime":"2022-07-20 20:49:46"}Order(id=1, createTime=Wed Jul 20 20:49:46 CST 2022, updateTime=2022-07-20T20:49:46)- 1
- 2
Jackson 的常用注解
@JsonIgnore
使用 @JsonIgnore 可以忽略某个 Java 对象中的属性,它将不参与 JSON 的序列化与反序列化
@Datapublic class Cat { private String name; @JsonIgnore private Integer age;}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
编写单元测试类
class CatTest { ObjectMapper objectMapper = new ObjectMapper(); @Test void testPojoToJson() throws JsonProcessingException { Cat cat = new Cat(); cat.setName("Tom"); cat.setAge(2); String json = objectMapper.writeValueAsString(cat); System.out.println(json); Assertions.assertEquals(json, "{\"name\":\"Tom\"}"); cat = objectMapper.readValue(json, Cat.class); Assertions.assertEquals(cat.getName(), "Tom"); Assertions.assertEquals(cat.getAge(), null); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
输出结果中 age 属性为 null
{"name":"Tom"}- 1
@JsonGetter
使用 @JsonGetter 可以在对 Java 对象进行 JSON 序列化时自定义属性名称
@Datapublic class Cat { private String name; private Integer age; @JsonGetter(value = "catName") public String getName() { return name; }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
编写单元测试类进行测试
class CatTest { ObjectMapper objectMapper = new ObjectMapper(); @Test void testPojoToJson2() throws JsonProcessingException { Cat cat = new Cat(); cat.setName("Tom"); cat.setAge(2); String json = objectMapper.writeValueAsString(cat); System.out.println(json); Assertions.assertEquals(json, "{\"age\":2,\"catName\":\"Tom\"}"); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
输出结果,name 已经设置成了 catName
{"age":2,"catName":"Tom"}- 1
@JsonSetter
使用 @JsonSetter 可以在对 JSON 进行反序列化时设置 JSON 中的 key 与 Java 属性的映射关系
@Datapublic class Cat { @JsonSetter(value = "catName") private String name; private Integer age; @JsonGetter(value = "catName") public String getName() { return name; }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
编写单元测试类进行测试
class CatTest { ObjectMapper objectMapper = new ObjectMapper(); @Test void testPojoToJson2() throws JsonProcessingException { String json = "{\"age\":2,\"catName\":\"Tom\"}"; Cat cat = objectMapper.readValue(json, Cat.class); System.out.println(cat.toString()); Assertions.assertEquals(cat.getName(), "Tom"); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
输出结果
Cat(name=Tom, age=2)- 1
@JsonAnySetter
使用 @JsonAnySetter 可以在对 JSON 进行反序列化时,对所有在 Java 对象中不存在的属性进行逻辑处理,下面的代码演示把不存在的属性存放到一个 Map 集合中
@Data@AllArgsConstructor@NoArgsConstructorpublic class Student { private String name; private Integer age; private Map<String, Object> diyMap = new HashMap<>(); @JsonAnySetter public void otherField(String key, String value) { this.diyMap.put(key, value); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
编写单元测试用例
class StudentTest { private ObjectMapper objectMapper = new ObjectMapper(); @Test void testJsonToPojo() throws JsonProcessingException { Map<String, Object> map = new HashMap<>(); map.put("name", "aLang"); map.put("age", 18); map.put("skill", "java"); String json = objectMapper.writeValueAsString(map); System.out.println(json); Student student = objectMapper.readValue(json, Student.class); System.out.println(student); Assertions.assertEquals(student.getDiyMap().get("skill"), "java"); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
输出结果中可以看到 JSON 中的 skill 属性因为不在 Java 类 Student 中,所以被放到了 diyMap 集合
{"skill":"java","name":"aLang","age":18}Student(name=aLang, age=18, diyMap={skill=java})- 1
- 2
@JsonAnyGetter
使用 @JsonAnyGetter 可以在对 Java 对象进行序列化时,使其中的 Map 集合作为 JSON 中属性的来源
@ToString@AllArgsConstructor@NoArgsConstructorpublic class Student { @Getter @Setter private String name; @Getter @Setter private Integer age; @JsonAnyGetter private Map<String, Object> initMap = new HashMap() {{ put("a", 111); put("b", 222); put("c", 333); }};}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
编写单元测试用例
class StudentTest { private ObjectMapper objectMapper = new ObjectMapper(); @Test void testPojoToJsonTest() throws JsonProcessingException { Student student = new Student(); student.setName("aLang"); student.setAge(20); String json = objectMapper.writeValueAsString(student); System.out.println(json); Assertions.assertEquals(json,"{\"name\":\"aLang\",\"age\":20,\"a\":111,\"b\":222,\"c\":333}"); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
输出结果
{"name":"aLang","age":20,"a":111,"b":222,"c":333}- 1
Jackson 总结
Jackson是Java中比较流量的JSON处理库之一,它是Spring的默认JSON工具Jackson主要有三个模块组成,Streaming API 、Annotations和Data BindingJackson中的ObjectMapper类十分强大,可以进行JSON相关处理,同时可以结合注释以及配置进行自定义转换逻辑。Jackson扩展性很好,如CSV、XML、YAML格式处理都对Jackson有相应的适配等