6. 二十不惑,ObjectMapper使用也不再迷惑
点击上方“BAT的乌托邦”,选择“设为星标”
✍前言
各位好,我是YourBatman。从本文起,终于要和Jackson的“高级”部分打交道了,也就是数据绑定jackson-databind
模块。通过接触它的高级API,你会持续的发现,前面花那么多篇幅讲的core核心部分是价值连城的。毕竟村上春树也告诉过我们:「人生没有无用的经历」嘛。
jackson-databind
包含用于Jackson数据处理器的通用 「数据绑定功能」和「树模型」。它构建在Streaming API之上,并使用Jackson注解
进行配置。它就是Jackson提供的高层API,是开发者使用得最多的方式,因此重要程度可见一斑。
虽然Jackson最初的用例是JSON数据绑定,但现在它也可以用于其它数据格式,只要存在解析器和生成器实现即可。但需要注意的是:类的命名在很多地方仍旧使用了“JSON”这个词(比如JsonGenerator),尽管它与JSON格式没有实际的硬依赖关系。
❝小贴士:底层流式API使用的I/O进行输入输出,因此理论上是支持任何格式的
❞
版本约定
-
Jackson版本: 2.11.0
-
Spring Framework版本: 5.2.6.RELEASE
-
Spring Boot版本: 2.3.0.RELEASE
从「本文开始」,新增导包:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
Tips:jackson-databind
模块它强依赖于jackson-core和jackson-annotations,只需要导入此包,另外两个它自动会帮带进来。
❝这里需要说明几句:我们知道core包中还有个
❞jackson-annotations
,难道不讲了吗?其实不是,是因为单独讲jackson-annotations
并无意义,毕竟注解还得靠数据绑定模块来解析,所以先搞定这个后再杀回去。
✍正文
据我了解,很多小伙伴对Jackson的了解起源于ObjectMapper
,止于ObjectMapper
。那行,作为接触它的第一篇文章咱们就轻松点,以应用为主来整体的认识它。
功能介绍
ObjectMapper是jackson-databind模块最为重要的一个类,它完成了coder对数据绑定的「几乎所有功能」。它是面向用户的高层API,底层依赖于Streaming API来实现读/写。ObjectMapper主要提供的功能点如下:
-
它提供读取和写入JSON的功能(最重要的功能) -
普通POJO的序列化/反序列化 -
JSON树模型的读/写 -
它可以被 「高度定制」,以使用不同风格的JSON内容 -
使用Feature进行定制 -
使用可插拔 com.fasterxml.jackson.databind.Module
模块来扩展/丰富功能 -
它还支持 「更高级」的对象概念:比如多态泛型、对象标识 -
它还充当了更为高级(更强大)的API:ObjectReader和ObjectWriter的 「工厂」 -
ObjectReader
和ObjectWriter
底层亦是依赖于Streaming API实现读写
尽管绝大部分的读/写API都通过ObjectMapper暴露出去了,但有些功能函数还是只放在了ObjectReader/ObjectWriter里,比如对于读/写 「长序列」 的能力你只能通过ObjectReader#readValues(InputStream) / ObjectWriter#writeValues(OutputStream)
去处理,这是设计者有意为之,毕竟这种case很少很少,没必要和常用的凑合在一起嘛。
数据绑定
数据绑定分为简单数据绑定和完全数据绑定:
-
「简单数据绑定」:比如绑定int类型、List、Map等…
@Test
public void test1() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
// 绑定简单类型 和 Map类型
Integer age = objectMapper.readValue("1", int.class);
Map map = objectMapper.readValue("{\"name\": \"YourBatman\"}", Map.class);
System.out.println(age);
System.out.println(map);
}
运行程序,输出:
1
{name=YourBatman}
-
「完全数据绑定」:绑定到任意的Java Bean对象…
准备一个POJO:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String name;
private Integer age;
}
绑定数据到POJO:
@Test
public void test2() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Person person = objectMapper.readValue("{\"name\": \"YourBatman\", \"age\": 18}", Person.class);
System.out.println(person);
}
运行程序,输出:
Person(name=YourBatman, age=18)
ObjectMapper的使用
在应用及开发中,ObjectMapper绝对是最常使用的,也是你使用Jackson的入口,本文就列列它的那些使用场景。
❝小贴士:树模型会单独成文介绍,体现出它的重要性
❞
写(序列化)
提供writeValue()
系列方法用于写数据(可写任何类型),也就是我们常说的「序列化」。
-
writeValue(File resultFile, Object value):写到目标文件里 -
writeValue(OutputStream out, Object value):写到输出流 -
「String writeValueAsString(Object value):写成字符串形式,此方法最为常用」 -
writeValueAsBytes(Object value):写成字节数组 byte[]
@Test
public void test3() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
System.out.println("----------写简单类型----------");
System.out.println(objectMapper.writeValueAsString(18));
System.out.println(objectMapper.writeValueAsString("YourBatman"));
System.out.println("----------写集合类型----------");
System.out.println(objectMapper.writeValueAsString(Arrays.asList(1, 2, 3)));
System.out.println(objectMapper.writeValueAsString(new HashMap<String, String>() {{
put("zhName", "A哥");
put("enName", "YourBatman");
}}));
System.out.println("----------写POJO----------");
System.out.println(objectMapper.writeValueAsString(new Person("A哥", 18)));
}
运行程序,输出:
----------写简单类型----------
18
"YourBatman"
----------写集合类型----------
[1,2,3]
{"zhName":"A哥","enName":"YourBatman"}
----------写POJO----------
{"name":"A哥","age":18}
读(反序列化)
提供readValue()
系列方法用于读数据(一般读字符串类型),也就是我们常说的「反序列化」。
-
readValue(String content, Class<T> valueType)
:读为指定class类型的对象,此方法最常用 -
readValue(String content, TypeReference<T> valueTypeRef)
:T表示泛型类型,如List<T>
这种类型,一般用于集合/Map的反序列化 -
readValue(String content, JavaType valueType):Jackson内置的JavaType类型,后再详解(使用并不多)
@Test
public void test4() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
System.out.println("----------读简单类型----------");
System.out.println(objectMapper.readValue("18", Integer.class));
// 抛错:JsonParseException 单独的一个串,解析会抛错
// System.out.println(objectMapper.readValue("YourBatman", String.class));
System.out.println("----------读集合类型----------");
System.out.println(objectMapper.readValue("[1,2,3]", List.class));
System.out.println(objectMapper.readValue("{\"zhName\":\"A哥\",\"enName\":\"YourBatman\"}", Map.class));
System.out.println("----------读POJO----------");
System.out.println(objectMapper.readValue("{\"name\":\"A哥\",\"age\":18}", Person.class));
}
运行程序,输出:
----------读简单类型----------
18
----------读集合类型----------
[1, 2, 3]
{zhName=A哥, enName=YourBatman}
----------读POJO----------
Person(name=A哥, age=18)
不同于序列化,可以把“所有”写成为一个字符串。反序列化场景有它特殊的地方,比如例子中所示:不能反序列化一个“单纯的”字符串。
泛型擦除问题
从例举出来的三个read读方法中,就应该觉得事情还没完,比如这个带泛型的case:
@Test
public void test5() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
System.out.println("----------读集合类型----------");
List<Long> list = objectMapper.readValue("[1,2,3]", List.class);
Long id = list.get(0);
System.out.println(id);
}
运行程序,抛错:
----------读集合类型----------
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
at cn.yourbatman.jackson.core.ObjectMapperDemo.test5(ObjectMapperDemo.java:100)
...
异常栈里指出:Long id = list.get(0);
这一句出现了「类型转换异常」,这便是问题原因所在:泛型擦除,参考图示如下(明明泛型类型是Long,但实际装的是Integer类型):对这种问题,你可能会“动脑筋”思考:写成
[1L,2L,3L]
这样行不行。思想很活跃,奈何现实依旧残酷,运行抛错:
com.fasterxml.jackson.core.JsonParseException: Unexpected character ('L' (code 76)): was expecting comma to separate Array entries
at [Source: (String)"[1L,2L,3L]"; line: 1, column: 4]
...
这是典型的泛型擦除问题。该问题只可能出现在读(反序列化)上,不能出现在写上。那么这种问题怎么破?
「在解决此问题之前,我们得先对Java中的泛型擦除有所了解,至少知道如下两点结论:」
-
Java 在编译时会在字节码里指令集之外的地方保留 「部分」泛型信息 -
泛型接口、类、方法定义上的所有泛型、成员变量声明处的泛型 「都会」被保留类型信息, 「其它地方」的泛型信息都会被擦除
此问题在开发过程中非常高频,有了此理论作为支撑,A哥提供两种可以解决本问题的方案供以参考:
方案一:利用成员变量保留泛型
理论依据:成员变量的泛型类型不会被擦除
@Test
public void test6() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
System.out.println("----------读集合类型----------");
Data data = objectMapper.readValue("{\"ids\" : [1,2,3]}", Data.class);
Long id = data.getIds().get(0);
System.out.println(id);
}
@lombok.Data
private static class Data {
private List<Long> ids;
}
运行程序,一切正常:
----------读集合类型----------
1
方案二:使用官方推荐的TypeReference<T>
官方早早就为我们考虑好了这类泛型擦除的问题,所以它提供了TypeReference<T>
方便我们把泛型类型保留下来,使用起来是非常的方便的:
@Test
public void test7() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
System.out.println("----------读集合类型----------");
List<Long> ids = objectMapper.readValue("[1,2,3]", new TypeReference<List<Long>>() {
});
Long id = ids.get(0);
System.out.println(id);
}
运行程序,一切正常:
----------读集合类型----------
1
本方案的理论依据是:泛型接口/类上的泛型类型不会被擦除。
对于泛型擦除情况,解决思路是「hold住」泛型类型,这样反序列化的时候才不会抓瞎。但凡只要一抓瞎,Jackson就木有办法只能采用「通用/默认类型」去装载喽。
加餐
自2.10
版本起,给ObjectMapper提供了一个子类:JsonMapper
,使得语义更加明确,专门用于处理JSON格式。
❝严格意义上讲,ObjectMapper不局限于处理JSON格式,比如后面会讲到的它的另外一个子类
❞YAMLMapper
用于对Yaml格式的支持(需额外导包,后面见~)
另外,由于构建一个ObjectMapper实例属于高频动作,因此Jackson也顺应潮流的提供了MapperBuilder
构建器(2.10版本起)。我们可以通过此构建起很容易的得到一个ObjectMapper(以JsonMapper为例)实例来使用:
@Test
public void test8() throws JsonProcessingException {
JsonMapper jsonMapper = JsonMapper.builder()
.configure(JsonReadFeature.ALLOW_SINGLE_QUOTES, true)
.build();
Person person = jsonMapper.readValue("{'name': 'YourBatman', 'age': 18}", Person.class);
System.out.println(person);
}
运行程序,正常输出:
Person(name=YourBatman, age=18)
✍总结
本文内容很轻松,讲述了ObjectMapper的日常使用,使用它进行读/写,完成日常功能。
对于写来说比较简单,一个writeValueAsString(obj)
方法走天下;但对于读来说,除了使用readValue(String content, Class<T> valueType)
自动完成数据绑定外,需要特别注意泛型擦除问题:「若反序列化成为一个集合类型(Collection or Map),泛型会被擦除」,此时你应该使用readValue(String content, TypeReference<T> valueTypeRef)
方法代替。
❝小贴士:若你在工程中遇到
❞objectMapper.readValue(xxx, List.class)
这种代码,那肯定是有安全隐患的(但不一定报错)
✔推荐阅读:
-
Fastjson到了说再见的时候了 -
1. 初识Jackson -- 世界上最好的JSON库 -
2. 妈呀,Jackson原来是这样写JSON的 -
3. 懂了这些,方敢在简历上说会用Jackson写JSON -
4. JSON字符串是如何被解析的?JsonParser了解一下 -
5. JsonFactory工厂而已,还蛮有料,这是我没想到的
关注A哥,开启专栏式学习
公众号后台回复“专栏”进入更多Spring专栏学习
右侧为私人微信(加好友备注:Java入群)
个人站点(开白申请):https://www.yourbatman.cn
本文分享自微信公众号 - BAT的乌托邦(BAT-utopia)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
图解 Vue 响应式原理
最近部门分享,有同学提到了 Vue 响应式原理,大家在讨论时,发现一些同学对这一知识理解还不够深入,不能形成一个闭环,为了帮助大家理解这个问题,我重新过了一下 Vue 源码,并整理了多张流程图,便于大家理解。 Vue 初始化 模板渲染 组件渲染 本文 Vue 源码版本:2.6.11,为了便于理解,均有所删减。 本文将从以下两个方面进行探索: 从 Vue 初始化,到首次渲染生成 DOM 的流程。 从 Vue 数据修改,到页面更新 DOM 的流程。 Vue 初始化 先从最简单的一段 Vue 代码开始: <template> <div> {{ message }} </div></template><script>new Vue({ data() { return { message: "hello world", }; },});</script> 这段代码很简单,最终会在页面上打印一个 hello world,它是如何实现的呢? 我们从源头:new Vue 的地方开始分析。 //执行newVue时会依次执行以下方法/...
- 下一篇
【走进小程序原理】揭秘组件同层渲染
阅读本文的收获:为什么我的小程序组件不能随着页面滚动?为什么组件层级不对?我该如何解决? 在日常开发中,我们总能在小程序的开发文档里看到种种组件: 基础组件:小程序框架层开发 自定义组件:开发者or小程序官方,基于基础组件进行二次开发 动态库组件:小程序官方开发的、以动态库形式发布的组件,其本质依然是自定义、基础组件 ...... 综上:就像是盖楼,框架开发的基础组件,是小程序所有组件建筑的地基,我们今天要聊的正是它。 基础组件实现 前置名词解释 NA:Native App的缩写,是基于智能手机本地操作系统如iOS、Android、WP并使用原生程式编写运行的第三方应用程序,一般开发语言为JAVA、C++、Objective-C、Swift NA 组件:也称原生组件,是Android、ios 等NA客户端开发的控件 H5组件:是指HTML5语言编写的web组件 webview:用来在NA代码中展示web页面,有点类似web中的iframe,ios、Android中分别采取WKWebView和WebView控件实现。 前置特性解释 小程序前端框架,会将开发者实现的小程序布局转换成标准 H...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Hadoop3单机部署,实现最简伪集群
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程