你必须很是努力,才能看起来绝不费力。本文已被 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年前同样,会用就能加分?java
一聊就会,一问就退,一写就废。这是不少公司程序员的真实写照,基/中层管理者尤甚。早早的和技术渐行渐远,致使裁人潮到来时很容易得到一张“飞机票”,年纪越大,焦虑感越强。git
在你的公司是否有过这种场景:四五我的指挥一我的干活。对,就像这样:
扎不扎心,老铁😄。不过不用悲观,从这应该你看到的是机会,习xx都说了实干才能兴邦嘛,2019年裁人潮洗牌后,适者生存,不适者不少回老家了,这也让大批颇有实力的程序员享受到了红利。应正了那句:当大潮褪去,才知道谁在裸泳。程序员
扯远了,言归正传。Jackson单会简单使用我认为还不足矣立足,那就跟我来吧~github
2.11.0
5.2.6.RELEASE
2.3.0.RELEASE
一个框架/库好很差,不是看它的核心功能作得怎么样,而是非核心功能处理得如何。好比后台页面作得咋样?容错机制呢?定制化、可配置化,扩展性等等。面试
Jackson称得上优秀(甚至最佳)最主要是得益于它优秀的module模块化设计,在接触其以前,咱们先完成本章节的内容:JsonGenerator
写JSON的行为控制(配置)。算法
配置属于程序的一部分,它影响着程序执行的方方面面。Spring
使用Environment/PropertySource管理配置,对应的在Jackson里会看到有不少Feature类来控制Jackson的读/写行为,均是使用enum枚举类型来管理。json
上篇文章 咱们学会了如何使用JsonGenerator去写一个JSON,本文未来学习它的须要掌握的使用细节。一样的,为围绕着JsonGenerator展开。segmentfault
它是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时的不一样行为,而且可分为三大类(源码处我也有标注):
Jackson的流式API指的是I/O流,所以就涉及到关流、flush刷新流等操做
JSON规范规定key都必须有双引号,但这对于某些场景下并不须要
JSON做为K-V结构的数据,那么容许相同key出现吗?这便由这些特征去控制
下面分别来认识认识它们。
含义即为字面意:自动关闭目标(流)。
JsonGenerator#close()
便会自动关闭底层的I/O流,你无需再关心自动关闭:
@Test public void test1() throws IOException { JsonFactory factory = new JsonFactory(); try (JsonGenerator jg = factory.createGenerator(System.err, JsonEncoding.UTF8)) { // doSomething } }
若是改成false:那么你就须要本身手动去close底层使用的OutputStream或者Writer。形如这样:
@Test public 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()方法,你应该能懂吧😄
先来看下面这段代码:
@Test public 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的做用了。
JsonToken#START_ARRAY
和JsonToken#START_OBJECT
类型的内容不过仍是要啰嗦一句:虽然Jackson经过此Feature作了容错,可是本身在使用时,请务必显示书写闭合
在使用带有缓冲区的I/O写数据时,缺乏“临门一脚”是初学者很容易犯的错误,好比下面这个例子:
@Test public 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哥"}
对于此问题这里小科普一下。由于向磁盘、网络写入数据的时候,出于效率的考虑,操做系统(话外音:这是操做系统为之)并非输出一个字节就马上写入到文件或者发送到网络,而是把输出的字节先放到内存的一个缓冲区里(本质上就是一个byte[]数组),等到缓冲区写满了,再一次性写入文件或者网络。对于不少IO设备来讲,一次写一个字节和一次写1000个字节,花费的时间几乎是彻底同样的,因此OutputStream有个flush()方法,能强制把缓冲区内容输出。
小贴士: InputStream是没有flush()方法的哦
一般状况下,咱们不须要调用这个flush()方法,由于缓冲区写满了,OutputStream会自动调用它,而且,在调用close()方法关闭OutputStream以前,也会自动调用flush()方法强制刷一次缓冲区。可是,在某些状况下,咱们必须手动调用flush()方法,好比上例子,好比发IM消息...
此属性自2.10
版本后已过时,使用JsonWriteFeature#QUOTE_FIELD_NAMES
代替,应用在JsonFactory上,后文详解
JSON对象字段名是否为使用""双引号括起来,这是JSON规范(RFC4627)规定的。
@Test public 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哥"}
此属性自2.10
版本后已过时,使用JsonWriteFeature#WRITE_NAN_AS_STRINGS
代替,应用在JsonFactory上,后文详解
这个特征挺有意思,看例子(以写Float为例):
@Test public 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%是数字),很难作到既安全又方便,所以有了此特征让你根据须要来控制。
此属性自2.10
版本后已过时,使用JsonWriteFeature#ESCAPE_NON_ASCII
代替,应用在JsonFactory上,后文详解
@Test public 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"
此属性自2.10
版本后已过时,使用JsonWriteFeature#WRITE_NUMBERS_AS_STRINGS
代替,应用在JsonFactory上,后文详解
该特性强制将全部Java数字写成字符串,即便底层数据格式真的是数字。
@Test public 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
控制写java.math.BigDecimal
的行为:
BigDecimal#toPlainString()
方法输出@Test public 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
是否去严格的检测重复属性名。
JsonParseException
异常@Test public 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%。
若是底层数据格式须要输出全部属性,以及若是找不到调用者试图写入的属性的定义,则该特性肯定是否要执行的操做。
可能你听完还一脸懵逼,什么底层数据格式,什么找不到,我明明是写JSON啊,何解?其实这不是针对于写JSON来讲的,对于JSON,这个特性没有效果,由于属性不须要预先定义。一般,大多数文本数据格式不须要模式信息,而某些二进制数据格式须要定义(如Avro、protobuf),所以这个属性是为它们而生(Smile、BSON等这些二进制也是不须要预约模式信息的哦)。
强调:
JsonGenerator
不是只能写JSON格式,毕竟底层是I/O流嘛,理论上啥都能写
能够预先调用(在写数据以前)这个API设定好模式信息便可:
JsonGenerator: public void setSchema(FormatSchema schema) { ... }
经过上一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) { ... };
本类是2.10版本新增的,用于彻底替换上面的Feature。目的:彻底独立的属性配置,不依赖于任何后端格式,由于JsonGenerator
并不局限于写JSON,所以把Feature放在JsonGenerator做为内部类是不太合适的,因此单独摘出来。
StreamWriteFeature用在JsonFactory
里,后面再讲解到它的构建器JsonFactoryBuilder
时再详细探讨。
上篇文章用代码演示过了如何使用writeObject(Object pojo)
来把一个POJO一次性序列化成为一个JSON串,它主要依赖于ObjectCodec去完成:
public abstract JsonGenerator setCodec(ObjectCodec oc);
ObjectCodec可谓是Jackson里极其重要的一个基础组件,咱们最熟悉的ObjectMapper
它就是一个解码器,实现了序列化和反序列化、树模型等操做。这将在后面章节里重点介绍~
咱们知道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
实例,因地制宜和互相隔离。
Author | A哥(YourBatman) |
---|---|
我的站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活跃平台 |
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |