有道无术,术尚可求,有术无道,止于术。
本系列 Jackson 版本 2.17.0
源码地址:https://gitee.com/pearl-organization/study-jackson-demo
1. 概述
在前两篇文档中,我们学习了JsonGenerator和JsonParser的简单用法,实际开发中很少用到它们,因为它们属于低级API,自由度高但用起来比较繁琐。 我们使用最多的还是ObjectMapper,它是jackson-databind数据绑定模块提供面向用户的高级API。
官方注释中说明了主要的功能特性:
- 提供了从
POJO或JSON树模型读取和写入JSON的功能,并支持互相转换 - 支持高度自定义,以使用不同样式的
JSON内容 - 支持更高级的对象概念,如多态泛型和对象标识
- 充当了更高级的
ObjectReader和ObjectWriter类的工厂
ObjectMapper类关系图如下所示:

其中TreeCodec、ObjectCodec声明了树模型、对象绑定的读写操作,ObjectMapper还有一个子类JsonMapper,专用于处理JSON格式。
2. 案例演示
首先需要引入jackson-databind数据绑定模块:
xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
2.1 创建对象
ObjectMapper提供了多个构造函数:

一般使用默认的构造函数即可,其他的方式后面会单独介绍:
java
ObjectMapper objectMapper = new ObjectMapper();
2.2 写入
ObjectMapper提供了多个写入方法,支持将对象以JSON格式写入到文件、输出流等对象中,以及返回字符串、字节数组:

将POJO对象转换为JSON字符串(序列化)示例如下:
java
// POJO->JSON
User user = new User();
user.setId(1767798780627279873L);
user.setName("坤坤");
user.setAge(18);
String json = objectMapper.writeValueAsString(user);
System.out.println(json);
控制台输出如下:
json
{"id":1767798780627279873,"name":"坤坤","age":18,"org":null,"roleList":null}
写入到文件示例如下:
java
// 写入到文件
File file = new File("E:\\TD\\pearl\\study-jackson-demo\\jackson-core-demo\\src\\main\\java\\com\\pearl\\jacksoncore\\demo\\file\\user.json");
objectMapper.writeValue(file,user);
2.3 读取
ObjectMapper也提供了多个读取方法,支持从多种地方读取JSON内容并转换为POJO对象:

从文件中读取JSON并转换为POJO示例:
java
// 文件读取JSON -> POJO
User readUserByFile = objectMapper.readValue(file, User.class);
System.out.println(readUserByFile);
从字符串中读取JSON并转换为POJO(反序列化)示例:
java
String jsonStr="{\"id\":1767798780627279873,\"name\":\"坤坤\",\"age\":18,\"org\":null,\"roleList\":null}";
User userPojo = objectMapper.readValue(jsonStr, User.class);
System.out.println(userPojo);
此外ObjectMapper实现了很多将JSON读取为树模型的方法:

树模型 使用树状结构来呈现对象,例如用户对象的树模型示意图:

在JSON不太好转换为某个标准的对象时,可以直接转换为统一的树模型,示例如下:
java
String userJsonStr = "{\"id\":1767798780627279873,\"name\":\"坤坤\",\"age\":18,\"org\":null,\"roleList\":[{\"id\":null,\"roleName\":\"管理员\",\"roleCode\":\"ROLE_ADMIN\"},{\"id\":null,\"roleName\":\"普通用户\",\"roleCode\":\"ROLE_USER\"}]}";
JsonNode jsonNode = objectMapper.readTree(userJsonStr);
long userId = jsonNode.get("id").asLong();// 获取id 节点对应的值并转为 Long
String userName = jsonNode.get("name").asText();// 获取 name 节点对应的值并转为 String
String roleName = jsonNode.get("roleList").get(0).get("roleName").asText(); // 获取 roleList 节点对应的值,再获取第一个元素,再获取roleName 的值并转为 String
3. 泛型擦除
相信大家对JAVA中的泛型已经十分了解,其本质是参数化类型,在后台返回给前端数据时,一般都会使用一个统一的响应结果封装类,并使用泛型标识返回数据的类型:
java
public class R<T> {
/**
* 状态码
*/
private Integer code;
/**
* 返回信息
*/
private String msg;
/**
* 数据
*/
private T data;
public static <T> R<T> response(Integer code, String msg, T data) {
R<T> result = new R<>();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
// 省略 getter/setter......
}
调用示例:
java
R<User> response = R.response(200, "操作成功", user);
但是在反序列化(将JSON转为POJO)并获取泛型指定的对象时,示例代码:
java
String jsonStr = "{\"code\":200,\"msg\":\"操作成功\",\"data\":{\"id\":1767798780627279873,\"name\":\"坤坤\",\"age\":18,\"org\":null,\"roleList\":[{\"id\":null,\"roleName\":\"管理员\",\"roleCode\":\"ROLE_ADMIN\"},{\"id\":null,\"roleName\":\"普通用户\",\"roleCode\":\"ROLE_USER\"}]}}";
R<User> response= objectMapper.readValue(jsonStr, R.class);
User data = response.getData();
这时会引发ClassCastException类型转换异常:
java
Exception in thread "main" java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.pearl.jacksoncore.demo.pojo.User (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.pearl.jacksoncore.demo.pojo.User is in unnamed module of loader 'app')
at com.pearl.jacksoncore.demo.databind.ObjectMapperTest.main(ObjectMapperTest.java:89)
通过Debug可以看到,虽然我们指定了接收对象的泛型为User,但是实际的data却是LinkedHashMap类型:

这是由于JAVA中的泛型擦除机制导致的,为了兼容老版本的Java和为了性能考虑,在编译期会进行类型擦除,在运行期间JVM并不知道泛型的存在,在对JSON字符串进行解析时,JVM自然也不知道需要将其解析为哪种类型,则默认解析为LinkedHashMap,所以导致ClassCastException类型转换异常。
针对JAVA泛型擦除问题,Jackson提供了TypeReference<T>抽象类指定转换时的泛型类型,这样在反序列化时,也就知道是什么类型了,使用示例如下:
java
R<User> response = objectMapper.readValue(jsonStr, new TypeReference<R<User>>() { });