你必須很是努力,才能看起來絕不費力。本文已被 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的烏托邦 |
每日文章推薦 | 每日文章推薦 |