有道无术,术尚可求,有术无道,止于术。
本系列Jackson 版本 2.0.0
源码地址:https://gitee.com/pearl-organization/study-seata-demo
文章目录
- 1. 概述
- 2. 案例演示
- 2.1 创建对象
- 2.2 写入
- 2.3 读取
- 3. 泛型擦除
1. 概述
在前两篇文档中,我们学习了JsonGenerator
和JsonParser
的简单用法,实际开发中很少用到它们,因为它们属于低级API
,自由度高但用起来比较繁琐。 我们使用最多的还是ObjectMapper
,它是jackson-databind
数据绑定模块提供面向用户的高级API
。
官方注释中说明了主要的功能特性:
- 提供了从
POJO
或JSON
树模型读取和写入JSON
的功能,并支持互相转换 - 支持高度自定义,以使用不同样式的
JSON
内容 - 支持更高级的对象概念,如多态泛型和对象标识
- 充当了更高级的
ObjectReader
和ObjectWriter
类的工厂
2. 案例演示
首先需要引入jackson-databind
数据绑定模块:
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.17.0</version></dependency>
2.1 创建对象
ObjectMapper
提供了多个构造函数:
一般使用默认的构造函数即可,其他的方式后面会单独介绍:
ObjectMapper objectMapper = new ObjectMapper();
2.2 写入
ObjectMapper
提供了多个写入方法,支持将对象以JSON
格式写入到文件、输出流等对象中,以及返回字符串、字节数组:
将POJO
对象转换为JSON
字符串(序列化)示例如下:
// POJO->JSONUser user = new User();user.setId(1767798780627279873L);user.setName("坤坤");user.setAge(18);String json = objectMapper.writeValueAsString(user);System.out.println(json);
控制台输出如下:
{"id":1767798780627279873,"name":"坤坤","age":18,"org":null,"roleList":null}
写入到文件示例如下:
// 写入到文件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
示例:
// 文件读取JSON -> POJOUser readUserByFile = objectMapper.readValue(file, User.class);System.out.println(readUserByFile);
从字符串中读取JSON
并转换为POJO
(反序列化)示例:
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
不太好转换为某个标准的对象时,可以直接转换为统一的树模型,示例如下:
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 节点对应的值并转为 LongString userName = jsonNode.get("name").asText();// 获取 name 节点对应的值并转为 StringString roleName = jsonNode.get("roleList").get(0).get("roleName").asText(); // 获取 roleList 节点对应的值,再获取第一个元素,再获取roleName 的值并转为 String
3. 泛型擦除
相信大家对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......
}
调用示例:
R<User> response = R.response(200, "操作成功", user);
但是在反序列化(将JSON
转为POJO
)并获取泛型指定的对象时,示例代码:
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
类型转换异常:
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>
抽象类指定转换时的泛型类型,这样在反序列化时,也就知道是什么类型了,使用示例如下:
R<User> response = objectMapper.readValue(jsonStr, new TypeReference<R<User>>() { });