Jackson使用详细介绍
- 一 . Jackson 介绍
- 二. Jackson Maven 依赖
- 三. ObjectMapper 对象映射器
- 四. Jackson JSON 基本操作
- 1. Jackson JSON 序列化
- 2. Jackson JSON 反序列化
- 3. JSON 转 List
- 4. JSON 转 Map
- 5. Jackson 忽略字段
- 6. Jackson 日期格式化
- Date 类型
- LocalDateTime 类型
- 时间格式化
- 7. Jackson 常用注解
- @JsonIgnore
- @JsonGetter
- @JsonSetter
- @JsonAnySetter
- @JsonAnyGetter
- Jackson 总结
一 . 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 Maven 依赖
在使用 Jackson 时,大多数情况下我们只需要添加 jackson-databind 依赖项,就可以使用 Jackson 功能了,它依赖了下面两个包。
<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 字符串。
四. Jackson JSON 基本操作
1. Jackson JSON 序列化
Jackson 作为一个 Java 中的 JSON 工具库,处理 JSON 字符串和 Java
对象是它最基本最常用的功能,下面通过一些例子来演示其中的用法。
编写一个 Person 类,定义三个属性,名称、年龄以及技能。
/**
* @author gf
* @date 2023/1/29
*/
@Data
public class Person {
private String name;
private String age;
private List<String> skillList;
}
将 Java 对象转换成 JSON 字符串。
/**
* @author gf
* @date 2023/1/29
*/
public class PersonTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void pojoToJsonString() throws JsonProcessingException {
Person person = new Person();
person.setName("LaoWang");
person.setAge(28);
person.setSkillList(Arrays.asList("java", "c++"));
String json = objectMapper.writeValueAsString(person);
System.out.println(json);
String expectedJson = "{\"name\":\"LaoWang\",\"age\":28,\"skillList\":[\"java\",\"c++\"]}";
Assertions.assertEquals(json, expectedJson);
}
}
输出的 JSON 字符串:
{"name":"LaoWang","age":28,"skillList":["java","c++"]}
Jackson 甚至可以直接把序列化后的 JSON 字符串写入文件或者读取成字节数组。
mapper.writeValue(new File("result.json"), myResultObject);
// 或者
byte[] jsonBytes = mapper.writeValueAsBytes(myResultObject);
// 或者
String jsonString = mapper.writeValueAsString(myResultObject);
2. Jackson JSON 反序列化
直接上代码:
public class PersonTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void jsonStringToPojo() throws JsonProcessingException {
String expectedJson = "{\"name\":\"LaoWang\",\"age\":28,\"skillList\":[\"java\",\"c++\"]}";
Person person = objectMapper.readValue(expectedJson, Person.class);
System.out.println(person);
Assertions.assertEquals(person.getName(), "LaoWang");
Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]");
}
}
输出结果:
Person(name=LaoWang, age=28, skillList=[java, c++])
上面的例子演示了如何使用 Jackson 把一个 JSON 字符串反序列化成 Java 对象,其实 Jackson 对文件中的 JSON 字符串、字节形式的 JSON 字符串反序列化同样简单。
3. JSON 转 List
上面演示 JSON 字符串都是单个对象的,如果 JSON 是一个对象列表那么使用 Jackson 该怎么处理呢?
已经存在一个文件 PersonList.json.
[
{
"name": "LaoWang",
"age": 28,
"skillList": [
"java",
"c++"
]
},
{
"name": "LaoAi",
"age": 31,
"skillList": [
"java",
"vue",
"react"
]
}
]
读取它然后转换成 List
public class PersonTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void fileToPojoList() throws IOException {
File file = new File("src/PersonList.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(), "LaoWang");
Assertions.assertEquals(personList.get(1).getName(), "LaoAi");
}
}
输出对象内容:
Person(name=LaoWang, age=28, skillList=[java, c++])
Person(name=LaoAi, age=31, skillList=[java, vue, react])
4. JSON 转 Map
JSON 转 Map 在我们没有一个对象的 Java 对象时十分实用,下面演示如何使用 Jackson 把 JSON 文本转成 Map对象。
public class PersonTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void jsonStringToMap() throws IOException {
String expectedJson = "{\"name\":\"LaoWang\",\"age\":28,\"skillList\":[\"java\",\"c++\"]}";
Map<String, Object> employeeMap = objectMapper.readValue(expectedJson, new TypeReference<Map>() {});
System.out.println(employeeMap.getClass());
for (Map.Entry<String, Object> entry : employeeMap.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
Assertions.assertEquals(employeeMap.get("name"), "LaoWang");
}
}
可以看到 Map 的输出结果:
class java.util.LinkedHashMap
name:LaoWang
age:28
skillList:[java, c++]
5. Jackson 忽略字段
如果在进行 JSON 转 Java 对象时,JSON 中出现了 Java 类中不存在的属性,那么在转换时会遇到 com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException 异常。
使用objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false) 可以忽略不存在的属性。
public class PersonTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void jsonStringToPojoIgnoreProperties() throws IOException {
String json = "{\"yyy\":\"xxx\",\"name\":\"LaoWang\",\"age\":28,\"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(), "LaoWang");
Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]");
}
}
正常输出:
Person(name=LaoWang, age=28, skillList=[java, c++])
6. Jackson 日期格式化
在 Java 8 之前我们通常使用 java.util.Date 类来处理时间,但是在 Java 8 发布时引入了新的时间类, java.time.LocalDateTime. 这两者在 Jackson 中的处理略有不同。
先创建一个有两种时间类型属性的 Order 类。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.Date;
/**
* @author gf
* @date 2023/1/29
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer id;
private Date createTime;
private LocalDateTime updateTime;
}
Date 类型
下面我们新建一个测试用例来测试两种时间类型的 JSON 转换。
import java.util.Date;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tomato.jackson.entity.Order;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* @author gf
* @date 2023/1/29
*/
public 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":1674977689582,"updateTime":null}
Order(id=1, createTime=Sun Jan 29 15:34:49 CST 2023, updateTime=null)
可以看到正常的进行了 JSON 的序列化与反序列化,但是 JSON 中的时间是一个时间戳格式,可能不是我们想要的。
LocalDateTime 类型
为什么没有设置 LocalDateTime 类型的时间呢?因为默认情况下进行 LocalDateTime 类的 JSON 转换会遇到报错。
/**
* @author gf
* @date 2023/1/29
*/
public 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.tomato.jackson.entity.Order["updateTime"])
这里我们需要添加相应的数据绑定支持包。
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.11.4</version>
</dependency>
然后在定义 ObjectMapper 时通过 findAndRegisterModules() 方法来注册依赖。
public 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);
}
}
运行可以得到正常序列化与反序列化日志,不过序列化后的时间格式依旧奇怪。
{"id":1,"createTime":1674978707833,"updateTime":[2023,1,29,15,51,47,833000000]}
Order(id=1, createTime=Sun Jan 29 15:51:47 CST 2023, updateTime=2023-01-29T15:51:47.833)
时间格式化
通过在字段上使用注解 @JsonFormat 来自定义时间格式。
@Data
@AllArgsConstructor
@NoArgsConstructor
public 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;
}
再次运行上面的列子可以得到时间格式化后的 JSON 字符串。
{"id":1,"createTime":"2023-01-29 15:57:12","updateTime":"2023-01-29 15:57:12"}
Order(id=1, createTime=Sun Jan 29 15:57:12 CST 2023, updateTime=2023-01-29T15:57:12)
7. Jackson 常用注解
@JsonIgnore
使用 @JsonIgnore 可以忽略某个 Java 对象中的属性,它将不参与 JSON 的序列化与反序列化。
示例:
/**
* @author gf
* @date 2023/1/29
*/
@Data
public class Cat {
private String name;
@JsonIgnore
private Integer age;
}
编写单元测试类。
public 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);
}
}
输出结果中 age 属性为 null。
{"name":"Tom"}
@JsonGetter
使用 @JsonGetter 可以在对 Java 对象进行 JSON 序列化时自定义属性名称。
示例:
@Data
public class Cat {
private String name;
private Integer age;
@JsonGetter(value = "catName")
public String getName() {
return name;
}
}
编写单元测试类进行测试。
public 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\"}");
}
}
输出结果,name 已经设置成了 catName:
{"age":2,"catName":"Tom"}
@JsonSetter
使用 @JsonSetter 可以在对 JSON 进行反序列化时设置 JSON 中的 key 与 Java 属性的映射关系。
示例:
@Data
public class Cat {
@JsonSetter(value = "catName")
private String name;
private Integer age;
@JsonGetter(value = "catName")
public String getName() {
return name;
}
}
编写单元测试类进行测试
public class CatTest {
ObjectMapper objectMapper = new ObjectMapper();
@Test
void testPojoToJson3() 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");
}
}
输出结果:
Cat(name=Tom, age=2)
@JsonAnySetter
使用 @JsonAnySetter 可以在对 JSON 进行反序列化时,对所有在 Java对象中不存在的属性进行逻辑处理,下面的代码演示把不存在的属性存放到一个 Map 集合中。
示例:
/**
* @author gf
* @date 2023/1/29
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public 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);
}
}
编写单元测试用例。
public class StudentTest {
private ObjectMapper objectMapper = new ObjectMapper();
@Test
void testJsonToPojo() throws JsonProcessingException {
Map<String, Object> map = new HashMap<>();
map.put("name", "LaoWang");
map.put("age", 28);
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");
}
}
结果中可以看到 JSON 中的 skill 属性因为不在 Java 类 Student 中,所以被放到了 diyMap 集合。
{"skill":"java","name":"LaoWang","age":28}
Student(name=LaoWang, age=28, diyMap={skill=java})
@JsonAnyGetter
使用 @JsonAnyGetter 可以在对 Java 对象进行序列化时,使其中的 Map 集合作为 JSON 中属性的来源。
示例:
@ToString
@AllArgsConstructor
@NoArgsConstructor
public 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);
}};
}
编写单元测试用例。
public class StudentTest {
private ObjectMapper objectMapper = new ObjectMapper();
@Test
void testPojoToJsonTest() throws JsonProcessingException {
Student student = new Student();
student.setName("LaoWang");
student.setAge(28);
String json = objectMapper.writeValueAsString(student);
System.out.println(json);
Assertions.assertEquals(json,"{\"name\":\"LaoWang\",\"age\":28,\"a\":111,\"b\":222,\"c\":333}");
}
}
输出结果:
{"name":"LaoWang","age":28,"a":111,"b":222,"c":333}
Jackson 总结
- Jackson 是 Java 中比较流量的 JSON 处理库之一,它是 Spring 的默认 JSON 工具。
- Jackson 主要有三个模块组成,Streaming API 、Annotations 和 Data Binding 。
- Jackson 中的 ObjectMapper 类十分强大,可以进行 JSON 相关处理,同时可以结合注释以及配置进行自定义转换逻辑。
- Jackson 扩展性很好,如 CSV、XML、YAML 格式处理都对 Jackson 有相应的适配等。