内容简介:各位好,我是A哥(YourBatman)。上篇文章 整体介绍了世界上最好的JSON库 -- Jackson,对它有了整体了解:知晓了它是个生态,其它的仅是个JSON库而已。
点击上方“ 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共三种):
-
流式API:读取并将JSON内容写入作为离散事件 ->
JsonParser
读取数据,而JsonGenerator
负责写入数据 -
树模型: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版本均和:point_up_2:保持一致,避免了版本交叉
❝
说明:类似2.11.0和2.11.x这种小版本号的差异,你权可认为没有区别
❞
工程结构
鉴于是首次展示工程示例代码,将基本结构展示如下:
全部源码地址在本系列的 「最后一篇」 文章中会全部公示出来
❞正文
Jackson提供了一种对性能有极致要求的方式:流式API。它用于对性能有极致要求的场景,这个时候就可以使用此种方式来对JSON进行读写。
概念解释:流式、增量模式、JsonToken
-
流式(Streaming):此概念和 Java 8中的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}
:smile:这就是 ObjectMapper
的原理雏形,是不是开始着道了?:smile:
「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模块的树模型没有意义,所以这里先卖个关子,保持好奇心哈:smile:
❞
思考题
国人很喜欢把Jackson的序列化(写JSON)效率和Fastjson进行对比,那么你敢使用本文的流式API和Fastjson比吗?结果你猜一下呢?
总结
本文介绍了jackson-core模块的流式API,以及JsonGenerator写JSON的使用,相信对你理解Jackson生成JSON方面是有帮助的。它作为JSON处理的基石,虽然并不推荐直接使用,但仅仅是 「应用开发级别」 不推荐哦,如果你是个框架、中间件开发者,这些原理你很可能绕不过。
还是那句话,本文介绍它的目的并不是建议大家去项目上使用,而是为了后面理解 ObjectMapper
夯实基础,毕竟做技术的要知其然,知其所以然了后,面对问题才能坦然。
拒绝浅尝辄止,我们是认真的 (关注公众号回复“知识星球”领券后再轻装入驻)
关注A哥,开启专栏式学习
扫码关注后,回复“专栏”进入更多Spring专栏学习
右侧为私人微信(加好友备注:Java入群)
个人站点 (开白申请) :https://www.yourbatman.cn
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
删除
[英] 维克托•迈尔-舍恩伯格(Viktor Mayer-Schönberger)著 / 袁杰 译 / 浙江人民出版社 / 2013-1 / 49.90元
《删除》讲述了遗忘的美德,为读者展现了大数据时代的取舍之道。 《删除》从大数据时代信息取舍的目的和方法分别诠释了“被遗忘的权利”。维克托首先回溯了人类追寻记忆的过程,之后提出数字技术与全球网络正在瓦解我们天生的遗忘能力。对此,他考察了促进遗忘终止4大驱动力——数字化,廉价的存储器,易于提取,全球性访问。之后,他提出了当前数字化记忆的两大威胁——信息权力与时间,并给出了应对威胁的6大对策——数......一起来看看 《删除》 这本书的介绍吧!