Jackson 解析 JSON 详细教程
Posted 程序猿阿朗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jackson 解析 JSON 详细教程相关的知识,希望对你有一定的参考价值。
点赞再看,动力无限。 微信搜「 程序猿阿朗 」。
本文 Github.com/niumoo/JavaNotes 和 未读代码博客 已经收录,有很多知识点和系列文章。
JSON 对于开发者并不陌生,如今的 WEB 服务、移动应用、甚至物联网大多都是以 JSON 作为数据交换的格式。学习 JSON 格式的操作工具对开发者来说是必不可少的。这篇文章将介绍如何使用 Jackson 开源工具库对 JSON 进行常见操作。
JSON 介绍
什么是 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"
Jackson 介绍
Jackson 和 FastJson 一样,是一个 Java 语言编写的,可以进行 JSON 处理的开源工具库,Jackson 的使用非常广泛,Spring 框架默认使用 Jackson 进行 JSON 处理。
Jackson 有三个核包,分别是 Streaming、Databid、Annotations,通过这些包可以方便的对 JSON 进行操作。
- Streaming 在
jackson-core
模块。 定义了一些流处理相关的 API 以及特定的 JSON 实现。 - Annotations 在
jackson-annotations
模块,包含了 Jackson 中的注解。 - Databind 在
jackson-databind
模块, 在Streaming
包的基础上实现了数据绑定,依赖于Streaming
和Annotations
包。
得益于 Jackson 高扩展性的设计,有很多常见的文本格式以及工具都有对 Jackson 的相应适配,如 CSV、XML、YAML 等。
Jackson Maven 依赖
在使用 Jackson 时,大多数情况下我们只需要添加 jackson-databind
依赖项,就可以使用 Jackson 功能了,它依赖了下面两个包。
- com.fasterxml.jackson.core:jackson-annotations
- com.fasterxml.jackson.core:jackson-core
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
为了方便这篇文章后续的代码演示,我们同时引入 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>
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 类,定义三个属性,名称、年龄以及技能。
/**
* @author https://www.wdbyte.com
*/
@Data
public class Person
private String name;
private Integer age;
private List<String> skillList;
将 Java 对象转换成 JSON 字符串。
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author https://www.wdbyte.com
*/
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);
输出的 JSON 字符串:
"name":"aLng","age":27,"skillList":["java","c++"]
Jackson 甚至可以直接把序列化后的 JSON 字符串写入文件或者读取成字节数组。
mapper.writeValue(new File("result.json"), myResultObject);
// 或者
byte[] jsonBytes = mapper.writeValueAsBytes(myResultObject);
// 或者
String jsonString = mapper.writeValueAsString(myResultObject);
Jackson JSON 反序列化
直接贴出代码:
package com.wdbyte.jackson;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author https://www.wdbyte.com
*/
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++]");
输出结果:
Person(name=aLang, age=27, skillList=[java, c++])
上面的例子演示了如何使用 Jackson 把一个 JSON 字符串反序列化成 Java 对象,其实 Jackson 对文件中的 JSON 字符串、字节形式的 JSON 字符串反序列化同样简单。
比如先准备了一个 JSON 内容文件 Person.json。
"name": "aLang",
"age": 27,
"skillList": [
"java",
"c++"
]
下面进行读取转换。
ObjectMapper objectMapper = new ObjectMapper();
@Test
void 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++]");
同样输出了 Person 内容。
Person(name=aLang, age=27, skillList=[java, c++])
JSON 转 List
上面演示 JSON 字符串都是单个对象的,如果 JSON 是一个对象列表那么使用 Jackson 该怎么处理呢?
已经存在一个文件 PersonList.json
.
[
"name": "aLang",
"age": 27,
"skillList": [
"java",
"c++"
]
,
"name": "darcy",
"age": 26,
"skillList": [
"go",
"rust"
]
]
读取它然后转换成 List<Person>
。
ObjectMapper objectMapper = new ObjectMapper();
@Test
void 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");
可以输出对象内容:
Person(name=aLang, age=27, skillList=[java, c++])
Person(name=darcy, age=26, skillList=[go, rust])
JSON 转 Map
JSON 转 Map 在我们没有一个对象的 Java 对象时十分实用,下面演示如何使用 Jackson 把 JSON 文本转成 Map 对象。
ObjectMapper objectMapper = new ObjectMapper();
@Test
void 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");
可以看到 Map 的输出结果:
class java.util.LinkedHashMap
name:aLang
age:27
skillList:[java, c++]
Jackson 忽略字段
如果在进行 JSON 转 Java 对象时,JSON 中出现了 Java 类中不存在的属性,那么在转换时会遇到 com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException
异常。
使用 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
可以忽略不存在的属性。
ObjectMapper objectMapper = new ObjectMapper();
@Test
void 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++]");
正常输出:
Person(name=aLang, age=27, skillList=[java, c++])
Jackson 日期格式化
在 Java 8 之前我们通常使用 java.util.Date
类来处理时间,但是在 Java 8 发布时引入了新的时间类 java.time.LocalDateTime
. 这两者在 Jackson 中的处理略有不同。
先创建一个有两种时间类型属性的 Order 类。
package com.wdbyte.jackson;
import java.time.LocalDateTime;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author https://www.wdbyte.com
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order
private Integer id;
private Date createTime;
private LocalDateTime updateTime;
Date 类型
下面我们新建一个测试用例来测试两种时间类型的 JSON 转换。
package com.wdbyte.jackson;
import java.time.LocalDateTime;
import java.util.Date;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author https://www.wdbyte.com
*/
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);
在这个测试代码中,我们只初始化了 Date 类型的时间,下面是输出的结果:
"id":1,"createTime":1658320852395,"updateTime":null
Order(id=1, createTime=Wed Jul 20 20:40:52 CST 2022, updateTime=null)
可以看到正常的进行了 JSON 的序列化与反序列化,但是 JSON 中的时间是一个时间戳格式,可能不是我们想要的。
LocalDateTime 类型
为什么没有设置 LocalDateTime
类型的时间呢?因为默认情况下进行 LocalDateTime 类的 JSON 转换会遇到报错。
/**
* @author https://www.wdbyte.com
*/
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);
运行后会遇到报错:
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"])
这里我们需要添加相应的数据绑定支持包。
添加依赖:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.3</version>
</dependency>
然后在定义 ObjectMapper 时通过 findAndRegisterModules()
方法来注册依赖。
import Jackson 解析 JSON 详细教程