关于jackson:2-妈呀Jackson原来是这样写JSON的

2次阅读

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

没有人永远 18 岁,但永远有人 18 岁。本文已被 https://www.yourbatman.cn 收录,外面一并有 Spring 技术栈、MyBatis、JVM、中间件等小而美的 专栏 供以收费学习。关注公众号【BAT 的乌托邦】一一击破,深刻把握,回绝浅尝辄止。

前言

各位好,我是 A 哥(YourBatman)。上篇文章 整体介绍了世界上最好的 JSON 库 — Jackson,对它有了整体理解:通晓了它是个生态,其它的仅是个 JSON 库而已。

有人说 Jackson 小众?那么请先看看上篇文章吧。学 Jackson 性价比特地高,因为它应用宽泛、会的人少,因而在团队内如果你能精通,附加价值的效应就会非常明显了 …

我挠头想了想,本系列来不了虚的,只能肝。本系列教程不仅仅传授根本应用,指标是搞完后可能解决日常 99.99% 的问题,毕竟每个小团队都最好能有某些方面的小专家,毕竟大家都不乏遇见过一个技术问题卡一天的状况。只有从底层把握,方能熟能生巧

命名为 core 的模块个别都不简略,jackson-core天然也不例外。它是三大外围模块之一,并且是 外围中的外围 ,提供了对 JSON 数据的 残缺反对 (包含各种读、写)。它是三者中最弱小的模块,具备 最低的 开销和 最快的 读 / 写操作。

此模块提供了 最具底层 的 Streaming JSON 解析器 / 生成器,这组流式 API 属于 Low-Level API,具备十分显著的特点:

  • 开销小,损耗小,性能极高
  • 因为是 Low-Level API,所以灵便度极高
  • 又因为是 Low-Level API,所以易错性高,可读性差

jackson-core模块提供了两种解决 JSON 的形式(纵缆整个 Jackson 共三种):

  1. 流式 API:读取并将 JSON 内容写入作为离散事件 -> JsonParser读取数据,而 JsonGenerator 负责写入数据
  2. 树模型:JSON 文件在内存里以树模式示意。此种形式也很灵便,它相似于 XML 的 DOM 解析,层层嵌套的

作为“底层”技术,利用级开发中的确接触不多。为了引起你的器重,提前预报一下:Spring MVC对 JSON 音讯的转换器 AbstractJackson2HttpMessageConverter 它就用到了底层流式 API -> JsonGenerator 写数据。想不想拿下 Spring 呢?我想你的答案应该是 Yes 吧~

置信做 难事必有所得,你我他都会用的技术、都能解决的问题,那绝成不了你的外围竞争力,天然在团队内就难成发光体。

版本约定

准则:均选以后最新版本(疏忽小版本)

  • Jackson 版本:2.11.0
  • Spring Framework 版本:5.2.6.RELEASE
  • Spring Boot 版本:2.3.0.RELEASE

    • 内置的 Jackson 和 Spring 版本均和???? 保持一致,防止了版本穿插

阐明:相似 2.11.0 和 2.11.x 这种小版本号的差别,你权可认为没有区别

工程构造

鉴于是首次展现工程示例代码,将根本构造展现如下:

全副源码地址在本系列的 最初一篇 文章中会全副公示进去

注释

Jackson 提供了一种对性能有极致要求的形式:流式 API。它用于对性能有极致要求的场景,这个时候就能够应用此种形式来对 JSON 进行读写。

概念解释:流式、增量模式、JsonToken

  • 流式(Streaming):此概念和 Java8 中的 Stream 流是不同的。这里指的是IO 流,因而具备最低的开销和最快的读 / 写操作(记得关流哦)
  • 增量模式(incremental mode):它示意每个局部一个一个地往上减少,相似于垒砖。应用此流式 API 读写 JSON 的形式应用的 均是增量模式
  • JsonToken:每一部分都是一个独立的 Token(有不同类型的 Token),最终被“拼凑”起来就是一个 JSON。这是流式 API 里很重要的一个抽象概念。

对于增量模式和 Token 概念,在 Spirng 的 SpEL 表达式中也有同样的概念,这在 Spring 相干专栏里你将会再次领会到


本文将看看它是如何写 JSON 数据的,也就是JsonGenerator

JsonGenerator 应用 Demo

JsonGenerator定义用于编写 JSON 内容的公共 API 的基类(抽象类)。实例应用的工厂办法创立,也就是JsonFactory

小贴士:纵观整个 Jackson,它更多的是应用抽象类而非接口,这是它的一大“特色”。因而你相熟的面向接口编程,到这都要转变为面向抽象类编程喽。

话不多说,先来一个 Demo 感触一把:

@Test
public void test1() throws IOException {JsonFactory factory = new JsonFactory();
    // 本处只需演示,向控制台写(当然你能够向文件等任意中央写都是能够的)JsonGenerator jsonGenerator = factory.createGenerator(System.out, JsonEncoding.UTF8);
    
    try {jsonGenerator.writeStartObject(); // 开始写,也就是这个符号 {jsonGenerator.writeStringField("name", "YourBatman");
        jsonGenerator.writeNumberField("age", 18);

        jsonGenerator.writeEndObject(); // 完结写,也就是这个符号}
    } finally {jsonGenerator.close();
    }
}

因为 JsonGenerator 实现了 AutoCloseable 接口,因而能够应用 try-with-resources 优雅敞开资源(这也是举荐的应用形式),代码革新如下:

@Test
public void test1() throws IOException {JsonFactory factory = new JsonFactory();
    // 本处只需演示,向控制台写(当然你能够向文件等任意中央写都是能够的)try (JsonGenerator jsonGenerator = factory.createGenerator(System.out, JsonEncoding.UTF8)) {jsonGenerator.writeStartObject(); // 开始写,也就是这个符号 {jsonGenerator.writeStringField("name", "YourBatman");
        jsonGenerator.writeNumberField("age", 18);

        jsonGenerator.writeEndObject(); // 完结写,也就是这个符号}
    }
}

运行程序,控制台 输入:

{"name":"YourBatman","age":18}

这是最简应用示例,这也就是所谓的 序列化 底层实现,从示例中对 增量模式 可能有所感触吧。

纯手动档有木有,灵活性和性能极高,但易出错。这就像头文字 D 的赛车一样,先要速度、高性能、灵活性,那必须上手动档。

JsonGenerator 具体介绍

JsonGenerator 是个抽象类,它的继承体系如下:

  • WriterBasedJsonGenerator:基于 java.io.Writer 解决字符编码(话外音:应用 Writer 输入 JSON)

    • 因为 UTF- 8 编码根本标准化了,因而 Jackson 外部也提供了 SegmentedStringWriter/UTF8Writer 来简化操作
  • UTF8JsonGenerator:基于 OutputStream + UTF- 8 解决字符编码(话外音:明确指定了应用 UTF- 8 编码把字节变为字符)

默认状况下(不指定编码),Jackson 默认会应用 UTF- 8 进行编码,也就是说会应用 UTF8JsonGenerator 作为理论的 JSON 生成器实现类,具体逻辑将在讲述 JsonFactory 章节中有所体现,敬请关注。

值得注意的是,形象基类 JsonGenerator 它只负责 JSON 的生成,至于把生成好的 JSON 写到哪里去它并不关怀。比方示例中我给写到了控制台,当然你也能够写到文件、写到网络等等。

Spring MVC 中的 JSON 音讯转换器就是向HttpOutputMessage(网络输入流)里写 JSON 数据

要害 API

JsonGenerator尽管仅是形象基类,但 Jackson 它倡议咱们应用 JsonFactory 工厂来创立其实例,并不需要使用者去关怀其底层实现类,因而咱们仅须要 面向此抽象类编程 即可,此为对使用者十分敌对的设计。

对于 JSON 生成器来说,写办法天然是它的灵魂所在。家喻户晓,JSON 属于 K - V 数据结构,因而针对于一个 JSON 来说,每一段都 k 额分为 写 key 写 value两大阶段。

写 JSON Key

JsonGenerator 一共提供了 3 个办法用于写 JSON 的 key:

@Test
public void test2() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jsonGenerator = factory.createGenerator(System.out, JsonEncoding.UTF8)) {jsonGenerator.writeStartObject();

        jsonGenerator.writeFieldName("zhName");

        jsonGenerator.writeEndObject();}
}

运行程序,输入:

{"zhName"}

能够发现,key 能够独立存在(无需 value),但 value 是不能独立存在的哦,上面你会看到成果。而 3 个办法中的 其它 2 个办法

public abstract void writeFieldName(SerializableString name) throws IOException;

public void writeFieldId(long id) throws IOException {writeFieldName(Long.toString(id));
}

这两个办法,你能够忘了吧,记住 writeFieldName() 就足够了。

总的来说,写 JSON 的 key 非常简单的,这得益于 JSON 的 key 有且仅可能是 String 类型,所以状况繁多。上面持续理解较为简单的写 Value 的状况。

写 JSON Value

咱们晓得在 Java 中数据存在的模式(类型)十分之多,比方 String、int、Reader、char[]…,而在 JSON 中 值的类型 只能是如下模式:

  • 字符串(如{"name":"YourBatman"}
  • 数字(如{"age":18}
  • 对象(JSON 对象)(如{"person":{ "name":"YourBatman", "age":18}}
  • 数组(如{"names":[ "YourBatman", "A 哥"]}
  • 布尔(如{"success":true}
  • null(如:{"name":null}

小贴士:像数组、对象等这些“高级”类型能够相互有限嵌套

很显著,Java 中的数据类型和 JSON 中的值类型并不是一一对应的关系,那么这就须要 JsonGenerator 在写入时起到一个桥梁(适配)作用:

上面针对不同的 Value 类型别离作出 API 解说,给出示例阐明。在此之前,请先记住两个论断,会更有利于你了解示例:

  • JSON 的程序,和你 write 的程序保持一致
  • 写任何类型的 Value 之前请记得先 write 写 key,否则可能有效

字符串


可把 Java 中的 String 类型、Reader 类型、char[]字符数组类型等等写为 JSON 的字符串模式。

@Test
public void test3() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jsonGenerator = factory.createGenerator(System.out, JsonEncoding.UTF8)) {jsonGenerator.writeStartObject();

        jsonGenerator.writeFieldName("zhName");
        jsonGenerator.writeString("A 哥");

        jsonGenerator.writeFieldName("enName");
        jsonGenerator.writeString("YourBatman");

        jsonGenerator.writeEndObject();}
}

运行程序,输入:

{"zhName":"A 哥","enName":"YourBatman"}

数字


参考上例,不解释。

对象(JSON 对象)

@Test
public void test4() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jsonGenerator = factory.createGenerator(System.out, JsonEncoding.UTF8)) {jsonGenerator.writeStartObject();

        jsonGenerator.writeFieldName("zhName");
        jsonGenerator.writeString("A 哥");

        // 写对象(记得先写 key 否则有效)jsonGenerator.writeFieldName("person");
        jsonGenerator.writeStartObject();
        jsonGenerator.writeFieldName("enName");
        jsonGenerator.writeString("YourBatman");
        jsonGenerator.writeFieldName("age");
        jsonGenerator.writeNumber(18);
        jsonGenerator.writeEndObject();

        jsonGenerator.writeEndObject();}
}

运行程序,输入:

{"zhName":"A 哥","person":{"enName":"YourBatman","age":18}}

对象属于一个比拟非凡的 value 值类型,能够实现各种嵌套。也就是咱们平时所说的 JSON 套 JSON

数组

写数组和写对象有点相似,也会有先 start 再 end 的闭环思路。

如何向数组里写入 Value 值?咱们晓得 JSON 数组里能够装任何数据类型,因而往里写值的办法都可应用,形如这样:

@Test
public void test5() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jsonGenerator = factory.createGenerator(System.out, JsonEncoding.UTF8)) {jsonGenerator.writeStartObject();

        jsonGenerator.writeFieldName("zhName");
        jsonGenerator.writeString("A 哥");

        // 写数组(记得先写 key 否则有效)jsonGenerator.writeFieldName("objects");
        jsonGenerator.writeStartArray();
        // 1、写字符串
        jsonGenerator.writeString("YourBatman");
        // 2、写对象
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("enName", "YourBatman");
        jsonGenerator.writeEndObject();
        // 3、写数字
        jsonGenerator.writeNumber(18);
        jsonGenerator.writeEndArray();

        jsonGenerator.writeEndObject();}
}

运行程序,输入:

{"zhName":"A 哥","objects":["YourBatman",{"enName":"YourBatman"},18]}

实践上 JSON 数组里的每个元素能够是不同类型,但 原则上 请确保是同一类型哦

对于 JSON 数组类型,很多时候外面装载的是数字或者一般字符串类型,因而 JsonGenerator 也很暖心的为此提供了专用办法(能够调用该办法来一次性便捷的写入单个数组):

@Test
public void test6() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jsonGenerator = factory.createGenerator(System.out, JsonEncoding.UTF8)) {jsonGenerator.writeStartObject();

        jsonGenerator.writeFieldName("zhName");
        jsonGenerator.writeString("A 哥");

        // 快捷写入数组(从第 index = 2 位开始,取 3 个)jsonGenerator.writeFieldName("values");
        jsonGenerator.writeArray(new int[]{1, 2, 3, 4, 5, 6}, 2, 3);

        jsonGenerator.writeEndObject();}
}

运行程序,输入:

{"zhName":"A 哥","values":[3,4,5]}

布尔和 null

比较简单,JsonGenerator 各提供了一个办法供你应用:

public abstract void writeBoolean(boolean state) throws IOException;
public abstract void writeNull() throws IOException;

示例代码:

@Test
public void test7() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jsonGenerator = factory.createGenerator(System.out, JsonEncoding.UTF8)) {jsonGenerator.writeStartObject();

        jsonGenerator.writeFieldName("success");
        jsonGenerator.writeBoolean(true);
        jsonGenerator.writeFieldName("myName");
        jsonGenerator.writeNull();

        jsonGenerator.writeEndObject();}
}

运行程序,输入:

{"success":true,"myName":null}

组合写 JSON Key 和 Value

在写每个 value 之前,都必须写 key。为了 简化书写,JsonGenerator 提供了二合一的组合办法,一个顶两:

@Test
public void test8() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jsonGenerator = factory.createGenerator(System.out, JsonEncoding.UTF8)) {jsonGenerator.writeStartObject();

        jsonGenerator.writeStringField("zhName","A 哥");
        jsonGenerator.writeBooleanField("success",true);
        jsonGenerator.writeNullField("myName");
        // jsonGenerator.writeObjectFieldStart();
        // jsonGenerator.writeArrayFieldStart();

        jsonGenerator.writeEndObject();}
}

运行程序,输入:

{"zhName":"A 哥","success":true,"myName":null}

理论应用时,举荐应用这些组合办法 去简化书写,毕竟新盖中盖高钙片,一片能顶过去 2 片,效率高。

其它写办法

如果说下面写办法是必修课,那上面的 write 写办法就当选修课吧。

writeRaw()和 writeRawValue()

该办法将强制生成器 不做任何批改 地逐字复制输出文本(包含不进行本义,也不增加分隔符,即便上下文 [array,object] 可能须要这样做)。如果须要这样的分隔符,请改用 writeRawValue 办法。

绝大多数状况下,应用 writeRaw()就够了,writeRawValue 的应用场景愈发的少

@Test
public void test9() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jsonGenerator = factory.createGenerator(System.out, JsonEncoding.UTF8)) {jsonGenerator.writeRaw("{'name':'YourBatman'}");
    }
}

运行程序,输入:

{'name':'YourBatman'}

如果换成 writeString() 办法,后果为(请留神比拟差别):

"{'name':'YourBatman'}"

writeBinary()

应用 Base64 编码把数据写进去。

writeEmbeddedObject()
2.8 版本新增的办法。看看此办法的源码你就晓得它是什么意思,不解释:

public void writeEmbeddedObject(Object object) throws IOException {// 01-Sep-2016, tatu: As per [core#318], handle small number of cases
    if (object == null) {writeNull();
        return;
    }
    if (object instanceof byte[]) {writeBinary((byte[]) object);
        return;
    }
    throw new JsonGenerationException(...);
}

writeObject()(重要):
写 POJO,但前提是你必须给 JsonGenerator 指定一个 ObjectCodec 解码器能力失常 work,否则抛出异样:

java.lang.IllegalStateException: No ObjectCodec defined for the generator, can only serialize simple wrapper types (type passed cn.yourbatman.jackson.core.beans.User)

    at com.fasterxml.jackson.core.JsonGenerator._writeSimpleObject(JsonGenerator.java:2238)
    at com.fasterxml.jackson.core.base.GeneratorBase.writeObject(GeneratorBase.java:391)
    ...

值得注意的是,Jackson 里咱们最为相熟的 API ObjectMapper它就是一个 ObjectCodec 解码器,具体咱们在 数据绑定 章节会再具体探讨,上面我给出个简略的应用示例模仿一把:

筹备一个 User 对象,以及解码器 UserObjectCodec:

@Data
public class User {
    private String name = "YourBatman";
    private Integer age = 18;
}

// 自定义 ObjectCodec 解码器 用于把 User 写为 JSON
// 因为本例只关注 write 写,因而只须要实现此这一个办法即可
public class UserObjectCodec extends ObjectCodec {
    ...
    @Override
    public void writeValue(JsonGenerator gen, Object value) throws IOException {User user = User.class.cast(value);

        gen.writeStartObject();
        gen.writeStringField("name",user.getName());
        gen.writeNumberField("age",user.getAge());
        gen.writeEndObject();}
    ...
}

测试用例:

@Test
public void test11() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jsonGenerator = factory.createGenerator(System.err, JsonEncoding.UTF8)) {jsonGenerator.setCodec(new UserObjectCodec());

        jsonGenerator.writeObject(new User());
    }
}

运行程序,输入:

{"name":"YourBatman","age":18}

???? 这就是 ObjectMapper 的原理雏形,是不是开始着道了?????

writeTree()
顾名思义,它便是 Jackson 赫赫有名的 树模型。惋惜的是 core 模块并没有提供树模型 TreeNode 的实现,以及它也是得依赖于 ObjectCodec 能力失常实现解码。

办法用来编写给定的 JSON 树(示意为树,其中给定的 JsonNode 是根)。这通常只调用给定节点的 writeObject,但增加它是为了不便起见,并使代码在专门解决树的状况下更显式。

可能你会想,曾经有了 writeObject() 办法还要它干啥呢?这其实是蛮有必要的,因为有时候你并不想定义 POJO 时,就能够用它疾速写 / 读数据,同时它也能够达到 含糊掉类型的概念,做到更形象和更专用。

说到含糊掉类型的的操作,你也能够辅以 Spring 的 AnnotationAttributes 的设计和应用来了解

筹备一个 TreeNode 的实现 UserTreeNode:

public class UserTreeNode implements TreeNode {

    private User user;

    public User getUser() {return user;}

    public UserTreeNode(User user) {this.user = user;}
    ...
}

UserObjectCodec 改写如下:

public class UserObjectCodec extends ObjectCodec {
    ...
    @Override
    public void writeValue(JsonGenerator gen, Object value) throws IOException {
        User user = null;
        if (value instanceof User) {user = User.class.cast(value);
        } else if (value instanceof TreeNode) {user = UserTreeNode.class.cast(value).getUser();}

        gen.writeStartObject();
        gen.writeStringField("name", user.getName());
        gen.writeNumberField("age", user.getAge());
        gen.writeEndObject();}
    ...
}

书写测试用例:

@Test
public void test12() throws IOException {JsonFactory factory = new JsonFactory();
    try (JsonGenerator jsonGenerator = factory.createGenerator(System.err, JsonEncoding.UTF8)) {jsonGenerator.setCodec(new UserObjectCodec());
        jsonGenerator.writeObject(new UserTreeNode(new User()));
    }
}

运行程序,输入:

{"name":"YourBatman","age":18}

本案例绕过了 TreeNode 的实在解决逻辑,是因为 树模型 这块会放在 databind 数据绑定模块进行更加具体的形容,前面再会喽。

阐明:Jackson 的树模型是比拟重要的,当然间接应用 core 模块的树模型没有意义,所以这里先卖个关子,放弃好奇心哈????

思考题

国人很喜爱把 Jackson 的序列化(写 JSON)效率和 Fastjson 进行比照,那么你敢应用本文的流式 API 和 Fastjson 比吗?后果你猜一下呢?

总结

本文介绍了 jackson-core 模块的流式 API,以及 JsonGenerator 写 JSON 的应用,置信对你了解 Jackson 生成 JSON 方面是有帮忙的。它作为 JSON 解决的基石,尽管并不举荐间接应用,但仅仅是 利用开发级别 不举荐哦,如果你是个框架、中间件开发者,这些原理你很可能绕不过。

还是那句话,本文介绍它的目标并不是倡议大家去我的项目上应用,而是为了前面了解 ObjectMapper 夯实根底,毕竟做技术的要知其然,知其所以然了后,面对问题能力坦然。


关注 A 哥

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

正文完
 0