关于jackson:6-二十不惑ObjectMapper使用也不再迷惑

32次阅读

共计 7769 个字符,预计需要花费 20 分钟才能阅读完成。

一滴水,用显微镜看,也是一个大世界。本文已被 https://www.yourbatman.cn 收录,外面一并有 Spring 技术栈、MyBatis、JVM、中间件等小而美的 专栏 供以收费学习。关注公众号【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 的 工厂

    • ObjectReaderObjectWriter 底层亦是依赖于 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 中的泛型擦除有所理解,至多晓得如下两点论断:

  1. Java 在编译时会在字节码里指令集之外的中央保留 局部 泛型信息
  2. 泛型接口、类、办法定义上的所有泛型、成员变量申明处的泛型 都会 被保留类型信息,其它中央 的泛型信息都会被擦除

此问题在开发过程中十分高频,有了此实践作为撑持,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 哥♥

Author A 哥(YourBatman)
集体站点 www.yourbatman.cn
E-mail yourbatman@qq.com
微 信 fsx641385712
沉闷平台
公众号 BAT 的乌托邦(ID:BAT-utopia)
常识星球 BAT 的乌托邦
每日文章举荐 每日文章举荐

正文完
 0