你必须十分致力,能力看起来毫不费力。本文已被 https://www.yourbatman.cn 收录,外面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以收费学习。关注公众号【BAT的乌托邦】一一击破,深刻把握,回绝浅尝辄止。
前言
各位好,我是A哥(YourBatman)。上篇文章:2. 妈呀,Jackson原来是这样写JSON的 晓得了Jackson写JSON的姿态,切实感触了一把ObjectMapper原来是这样实现序列化的...本文持续深刻探讨JsonGenerator写JSON的细节。
先闲聊几句题外话哈。咱们在书写简历的时候,都会用肯定篇幅展现本人的技能点(亮点),就像这样:
这一part十分重要,它决定了面试官是否有跟你聊的趣味,决定了你是否能在浩如烟海的简历中够怀才不遇。如何做到差异性?在当下如此发达的信息社会里,信息的获取唾手可得,所以在常识的广度方面,我认为人与人之间的差别其实并不大:
你晓得DDD畛域驱动、读过架构整洁之道、晓得六边形架构、晓得DevOps......难道你还在想凭一些概念卖钱?拉出差距?
你在用Spring技术栈、在用Redis、在用ElasticSearch......难道你还认为当初像10年前一样,会用就能加分?
一聊就会,一问就退,一写就废。这是很多公司程序员的真实写照,基/中层管理者尤甚。早早的和技术渐行渐远,导致裁员潮到来时很容易取得一张“飞机票”,年纪越大,焦虑感越强。
在你的公司是否有过这种场景:四五个人指挥一个人干活。对,就像这样:
扎不扎心,老铁????。不过不必乐观,从这应该你看到的是机会,习xx都说了实干能力兴邦嘛,2019年裁员潮洗牌后,适者生存,不适者很多回老家了,这也让少量很有实力的程序员享受到了红利。应正了那句:当大潮褪去,才晓得谁在裸泳。
扯远了,言归正传。Jackson单会简略应用我认为还有余矣立足,那就跟我来吧~
版本约定
- Jackson版本:
2.11.0
- Spring Framework版本:
5.2.6.RELEASE
- Spring Boot版本:
2.3.0.RELEASE
注释
一个框架/库好不好,不是看它的外围性能做得怎么样,而是非核心性能解决得如何。比方后盾页面做得咋样?容错机制呢?定制化、可配置化,扩展性等等。
Jackson称得上优良(甚至最佳)最次要是得益于它优良的module模块化设计,在接触其之前,咱们先实现本章节的内容:JsonGenerator
写JSON的行为管制(配置)。
配置属于程序的一部分,它影响着程序执行的方方面面。Spring
应用Environment/PropertySource治理配置,对应的在Jackson里会看到有很多Feature类来管制Jackson的读/写行为,均是应用enum枚举类型来治理。
上篇文章 咱们学会了如何应用JsonGenerator去写一个JSON,本文未来学习它的须要把握的应用细节。同样的,为围绕着JsonGenerator开展。
JsonGenerator的Feature
它是JsonGenerator的一个外部枚举类,共10个枚举值:
public enum Feature { // Low-level I/O AUTO_CLOSE_TARGET(true), AUTO_CLOSE_JSON_CONTENT(true), FLUSH_PASSED_TO_STREAM(true), // Quoting-related features @Deprecated QUOTE_FIELD_NAMES(true), @Deprecated QUOTE_NON_NUMERIC_NUMBERS(true), @Deprecated ESCAPE_NON_ASCII(false), @Deprecated WRITE_NUMBERS_AS_STRINGS(false), // Schema/Validity support features WRITE_BIGDECIMAL_AS_PLAIN(false), STRICT_DUPLICATE_DETECTION(false), IGNORE_UNKNOWN(false); ...}
小贴士:枚举值均为bool类型,括号内为默认值
这个Feature的每个枚举值都管制着JsonGenerator
写JSON时的不同行为,并且可分为三大类(源码处我也有标注):
- Low-level I/O:底层I/O流相干。
Jackson的流式API指的是I/O流,因而就波及到关流、flush刷新流等操作
- Quoting-related:双引号""援用相干。
JSON标准规定key都必须有双引号,但这对于某些场景下并不需要
- Schema/Validity support:束缚/标准/校验相干。
JSON作为K-V构造的数据,那么容许雷同key呈现吗?这便由这些特色去管制
上面别离来意识意识它们。
AUTO_CLOSE_TARGET(true)
含意即为字面意:主动敞开指标(流)。
- true:调用
JsonGenerator#close()
便会主动敞开底层的I/O流,你无需再关怀 - false:底层I/O流请手动敞开
主动敞开:
@Testpublic void test1() throws IOException { JsonFactory factory = new JsonFactory(); try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) { // doSomething }}
如果改为false:那么你就须要本人手动去close底层应用的OutputStream或者Writer。形如这样:
@Testpublic void test2() throws IOException { JsonFactory factory = new JsonFactory(); try (PrintStream err = System.err; JsonGenerator jg = factory.createGenerator(err, JsonEncoding.UTF8)) { // 特色置为false 采纳手动关流的形式 jg.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); // doSomething }}
小贴士:例子均采纳try-with-resources
形式关流,所以并没有显示调用close()办法,你应该能懂吧????
AUTO_CLOSE_JSON_CONTENT(true)
先来看上面这段代码:
@Testpublic void test3() throws IOException { JsonFactory factory = new JsonFactory(); try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) { jg.writeStartObject(); jg.writeFieldName("names"); // 写数组 jg.writeStartArray(); jg.writeString("A哥"); jg.writeString("YourBatman"); }}
运行程序,输入:
{"names":["A哥","YourBatman"]}
wow,居然输入一切正常。仔细的你会发现,我的代码是缺胳膊少腿的:不论是Object还是Array都只start了,并没有显示调用end进行闭合。然而呢,后果却失常得很,这便是此Feature的作用了。
- true:主动补齐(闭合)
JsonToken#START_ARRAY
和JsonToken#START_OBJECT
类型的内容 - false:啥都不做(不会被动抛错哦)
不过还是要啰嗦一句:尽管Jackson通过此Feature做了容错,然而本人在应用时,请务必显示书写闭合
FLUSH_PASSED_TO_STREAM(true)
在应用带有缓冲区的I/O写数据时,短少“临门一脚”是初学者很容易犯的谬误,比方上面这个例子:
@Testpublic void test4() throws IOException { JsonFactory factory = new JsonFactory(); JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8); jg.writeStartObject(); jg.writeStringField("name","A哥"); jg.writeEndObject(); // jg.flush(); // jg.close();}
运行程序,控制台没有任何输入。把正文代码放开任何一行,再次运行程序,控制台失常输入:
{"name":"A哥"}
- true:当JsonGenerator调用close()/flush()办法时,主动强刷I/O流外面的数据
- false:请手动解决
为何须要flush()?
对于此问题这里小科普一下。因为向磁盘、网络写入数据的时候,出于效率的思考,操作系统(话外音:这是操作系统为之)并不是输入一个字节就立即写入到文件或者发送到网络,而是把输入的字节先放到内存的一个缓冲区里(实质上就是一个byte[]数组),等到缓冲区写满了,再一次性写入文件或者网络。对于很多IO设施来说,一次写一个字节和一次写1000个字节,破费的工夫简直是齐全一样的,所以OutputStream有个flush()办法,能强制把缓冲区内容输入。
小贴士:InputStream是没有flush()办法的哦
通常状况下,咱们不须要调用这个flush()办法,因为缓冲区写满了,OutputStream会主动调用它,并且,在调用close()办法敞开OutputStream之前,也会主动调用flush()办法强制刷一次缓冲区。然而,在某些状况下,咱们必须手动调用flush()办法,比方上例子,比方发IM音讯...
QUOTE_FIELD_NAMES(true)
此属性自2.10
版本后已过期,应用JsonWriteFeature#QUOTE_FIELD_NAMES
代替,利用在JsonFactory上,后文详解
JSON对象字段名是否为应用""双引号括起来,这是JSON标准(RFC4627)规定的。
- true:字段名应用""括起来 -> 遵循JSON标准
- false:字段名不应用""括起来 -> 不遵循JSON标准
@Testpublic void test5() throws IOException { JsonFactory factory = new JsonFactory(); try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) { // jg.disable(QUOTE_FIELD_NAMES); jg.writeStartObject(); jg.writeStringField("name","A哥"); jg.writeEndObject(); }}
运行程序,输入:
{"name":"A哥"}
99.99%的状况下咱们不须要扭转默认值。Jackson增加了禁用引号的性能以反对那十分不常见的状况,最常见的状况间接从Javascript中应用时可能会产生。
关上正文掉的语句,再次运行程序,输入:
{name:"A哥"}
QUOTE_NON_NUMERIC_NUMBERS(true)
此属性自2.10
版本后已过期,应用JsonWriteFeature#WRITE_NAN_AS_STRINGS
代替,利用在JsonFactory上,后文详解
这个特色挺有意思,看例子(以写Float为例):
@Testpublic void test6() throws IOException { JsonFactory factory = new JsonFactory(); try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) { // jg.disable(JsonGenerator.Feature.QUOTE_NON_NUMERIC_NUMBERS); jg.writeNumber(0.9); jg.writeNumber(1.9); jg.writeNumber(Float.NaN); jg.writeNumber(Float.NEGATIVE_INFINITY); jg.writeNumber(Float.POSITIVE_INFINITY); }}
运行程序,输入:
0.9 1.9 "NaN" "-Infinity" "Infinity"
同为Float数字类型,有的输入有""双引号包着,有的没有。放开正文的语句(禁用此特色),再次运行程序,输入:
0.9 1.9 NaN -Infinity Infinity
很显著,如果你是这么输入为一个JSON的话,那它就会是非法的JSON,是不合乎JSON规范的(因为像NaN、Infinity这种显著是字符串嘛,必须用""包起来才是非法的value值)。
因为JSON标准中对数字的严格定义,加上Java可能具备的开放式数字集(如上例中Float类型并不100%是数字),很难做到既平安又不便,因而有了此特色让你依据须要来管制。
ESCAPE_NON_ASCII(false)
此属性自2.10
版本后已过期,应用JsonWriteFeature#ESCAPE_NON_ASCII
代替,利用在JsonFactory上,后文详解
@Testpublic void test7() throws IOException { JsonFactory factory = new JsonFactory(); try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) { // jg.enable(ESCAPE_NON_ASCII); jg.writeString("A哥"); }}
运行程序,输入:
"A哥"
放开注掉的代码(开启此属性),再次运行,输入:
"A\u54E5"
WRITE_NUMBERS_AS_STRINGS(false)
此属性自2.10
版本后已过期,应用JsonWriteFeature#WRITE_NUMBERS_AS_STRINGS
代替,利用在JsonFactory上,后文详解
该个性强制将所有Java数字写成字符串,即便底层数据格式真的是数字。
- true:所有数字强制写为字符串
- false:不做解决
@Testpublic void test8() throws IOException { JsonFactory factory = new JsonFactory(); try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) { // jg.enable(WRITE_NUMBERS_AS_STRINGS); Long num = Long.MAX_VALUE; jg.writeNumber(num); }}
运行程序,输入:
9223372036854775807
放开正文代码(开启此特色),再次运行程序,输入:
"9223372036854775807"
有什么应用场景?一个用例是防止Javascript限度的问题:因为Javascript标准规定所有的数字解决都应该应用64位ieee754浮点值来实现,后果是一些64位整数值不能被准确示意(因为尾数只有51位宽)。
采坑揭示:工夫戳后端用Long类型反给前端是没有问题的。但如果你是很大的一个Long值(如雪花算法算出的很大的Long值),间接返回前端的话,Javascript就会呈现精度失落的bug
WRITE_BIGDECIMAL_AS_PLAIN(false)
管制写java.math.BigDecimal
的行为:
- true:应用
BigDecimal#toPlainString()
办法输入 - false: 应用默认输入形式(取决于BigDecimal是如何结构的)
@Testpublic void test7() throws IOException { JsonFactory factory = new JsonFactory(); try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) { // jg.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN); BigDecimal bigDecimal1 = new BigDecimal(1.0); BigDecimal bigDecimal2 = new BigDecimal("1.0"); BigDecimal bigDecimal3 = new BigDecimal("1E11"); jg.writeNumber(bigDecimal1); jg.writeNumber(bigDecimal2); jg.writeNumber(bigDecimal3); }}
运行程序,输入:
1 1.0 1E+11
放开正文代码,再次运行程序,输入:
1 1.0 100000000000
STRICT_DUPLICATE_DETECTION(false)
是否去严格的检测反复属性名。
- true:检测是否有反复字段名,若有,则抛出
JsonParseException
异样 - false:不检测JSON对象反复的字段名,即:雷同字段名都要解析
@Testpublic void test8() throws IOException { JsonFactory factory = new JsonFactory(); try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) { // jg.enable(JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION); jg.writeStartObject(); jg.writeStringField("name","YourBatman"); jg.writeStringField("name","A哥"); jg.writeEndObject(); }}
运行程序,输入:
{"name":"YourBatman","name":"A哥"}
关上正文掉的哪行代码:开启此特征值为true。再次运行程序,输入:
com.fasterxml.jackson.core.JsonGenerationException: Duplicate field 'name' at com.fasterxml.jackson.core.json.JsonWriteContext._checkDup(JsonWriteContext.java:224) at com.fasterxml.jackson.core.json.JsonWriteContext.writeFieldName(JsonWriteContext.java:217) ...
留神:审慎关上此开关,如果查看的话性能会降落20%-30%。
IGNORE_UNKNOWN(false)
如果底层数据格式须要输入所有属性,以及如果找不到调用者试图写入的属性的定义,则该个性确定是否要执行的操作。
可能你听完还一脸懵逼,什么底层数据格式,什么找不到,我明明是写JSON啊,何解?其实这不是针对于写JSON来说的,对于JSON,这个个性没有成果,因为属性不须要事后定义。通常,大多数文本数据格式不须要模式信息,而某些二进制数据格式须要定义(如Avro、protobuf),因而这个属性是为它们而生(Smile、BSON等这些二进制也是不须要预约模式信息的哦)。
强调:JsonGenerator
不是只能写JSON格局,毕竟底层是I/O流嘛,实践上啥都能写
- true:启动该性能
能够事后调用(在写数据之前)这个API设定好模式信息即可:
JsonGenerator: public void setSchema(FormatSchema schema) { ... }
- false:禁用该性能。如果底层数据格式须要所有属性的常识能力输入,那就抛出JsonProcessingException异样
定制Feature
通过上一part通晓了管制JsonGenerator
的特征值们,以及其作用是。Feature的每个枚举值都有个默认值(括号外面),那么如果咱们心愿对不同的JsonGenerator实例利用不同的配置该怎么办呢?
自然而然的JsonGenerator提供了相干API供以咱们操作:
// 开启public abstract JsonGenerator enable(Feature f);// 敞开public abstract JsonGenerator disable(Feature f);// 开启/敞开public final JsonGenerator configure(Feature f, boolean state) { ... };public abstract boolean isEnabled(Feature f);public boolean isEnabled(StreamWriteFeature f) { ... };
替换者:StreamWriteFeature
本类是2.10版本新增的,用于齐全替换下面的Feature。目标:齐全独立的属性配置,不依赖于任何后端格局,因为JsonGenerator
并不局限于写JSON,因而把Feature放在JsonGenerator作为外部类是不太适合的,所以独自摘出来。
StreamWriteFeature用在JsonFactory
里,前面再解说到它的构建器JsonFactoryBuilder
时再具体探讨。
序列化POJO对象
上篇文章用代码演示过了如何应用writeObject(Object pojo)
来把一个POJO一次性序列化成为一个JSON串,它次要依赖于ObjectCodec去实现:
public abstract JsonGenerator setCodec(ObjectCodec oc);
ObjectCodec堪称是Jackson里极其重要的一个根底组件,咱们最相熟的ObjectMapper
它就是一个解码器,实现了序列化和反序列化、树模型等操作。这将在前面章节里重点介绍~
输入丑陋的JSON格局
咱们晓得JSON之所以疾速风行的起因之一是得益于它的可读性好,可读性好又体现在它丑陋的(规定)的展现格局上。
默认状况下,应用JsonGenerator
写JSON时,所有的局部都是输入在同一行里,显然这种格局对人浏览来说是不够敌对的。作为最风行的JSON库天然思考到了这一点,提供了格式化器来丑化输入:
// 本人指定丑陋格局打印器public JsonGenerator setPrettyPrinter(PrettyPrinter pp) { ... }// 利用默认的丑陋格局打印器public abstract JsonGenerator useDefaultPrettyPrinter();
PrettyPrinter有如下两个实现类:
应用不同的实现类,对输入后果的影响如下:
什么都不设置:MinimalPrettyPrinter:{"zhName":"A哥","enName":"YourBatman","age":18}DefaultPrettyPrinter:useDefaultPrettyPrinter():{ "zhName" : "A哥", "enName" : "YourBatman", "age" : 18}
由此可见,在什么都不设置的状况下,后果会全副在一行显示(紧凑型输入)。DefaultPrettyPrinter
示意带层级格局的输入(可读性好),若有此须要,倡议间接调用更为快捷的useDefaultPrettyPrinter()
办法,而不必本人去new一个实例。
总结
本文的次要内容和重点是介绍了用Feature去管制JsonGenerator的写行为,不同的特征值管制着不同的行为。在理论应用时可针对不同的需要,定制出不同的JsonGenerator
实例,就地取材和相互隔离。
相干举荐:
- Fastjson到了说再见的时候了
- 1. 初识Jackson -- 世界上最好的JSON库
- 2. 妈呀,Jackson原来是这样写JSON的
关注A哥
Author | A哥(YourBatman) |
---|---|
集体站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
沉闷平台 | |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
常识星球 | BAT的乌托邦 |
每日文章举荐 | 每日文章举荐 |