没有人永远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共三种):
- 流式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版本均和????保持一致,防止了版本穿插
阐明:相似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感触一把:
@Testpublic 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
优雅敞开资源(这也是举荐的应用形式),代码革新如下:
@Testpublic 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
来简化操作
- 因为UTF-8编码根本标准化了,因而Jackson外部也提供了
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:
@Testpublic 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的字符串模式。
@Testpublic 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 对象)
@Testpublic 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数组里能够装任何数据类型,因而往里写值的办法都可应用,形如这样:
@Testpublic 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
也很暖心的为此提供了专用办法(能够调用该办法来一次性便捷的写入单个数组):
@Testpublic 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;
示例代码:
@Testpublic 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提供了二合一的组合办法,一个顶两:
@Testpublic 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的应用场景愈发的少
@Testpublic 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:
@Datapublic 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(); } ...}
测试用例:
@Testpublic 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(); } ...}
书写测试用例:
@Testpublic 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 |
yourbatman@qq.com | |
微 信 | fsx641385712 |
沉闷平台 | |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
常识星球 | BAT的乌托邦 |
每日文章举荐 | 每日文章举荐 |