沒有人永遠18歲,但永遠有人18歲。本文已被 https://www.yourbatman.cn 收錄,裏面一併有Spring技術棧、MyBatis、JVM、中間件等小而美的 專欄供以避免費學習。關注公衆號【 BAT的烏托邦】逐個擊破,深刻掌握,拒絕淺嘗輒止。
各位好,我是A哥(YourBatman)。上篇文章 總體介紹了世界上最好的JSON庫 -- Jackson,對它有了總體瞭解:知曉了它是個生態,其它的僅是個JSON庫而已。java
有人說Jackson小衆?那麼請先看看上篇文章吧。學Jackson性價比特別高,由於它使用普遍、會的人少,所以在團隊內若是你能精通,附加價值的效應就會很是明顯了...
我撓頭想了想,本系列來不了虛的,只能肝。本系列教程不只僅教授基本使用,目標是搞完後可以解決平常99.99%的問題,畢竟每一個小團隊都最好能有某些方面的小專家,畢竟你們都不乏碰見過一個技術問題卡一天的狀況。只有從底層把握,方能遊刃有餘。
git
命名爲core的模塊通常都不簡單,jackson-core
天然也不例外。它是三大核心模塊之一,而且是核心中的核心,提供了對JSON數據的完整支持(包括各類讀、寫)。它是三者中最強大的模塊,具備最低的開銷和最快的讀/寫操做。 github
此模塊提供了最具底層的Streaming JSON解析器/生成器,這組流式API屬於Low-Level API,具備很是顯著的特色:編程
jackson-core模塊提供了兩種處理JSON的方式(縱纜整個Jackson共三種):json
JsonParser
讀取數據,而JsonGenerator
負責寫入數據做爲「底層」技術,應用級開發中確實接觸很少。爲了引發你的重視,提早預告一下:Spring MVC
對JSON消息的轉換器AbstractJackson2HttpMessageConverter
它就用到了底層流式API -> JsonGenerator寫數據。想不想拿下Spring呢?我想你的答案應該是Yes吧~
segmentfault
相信作難事必有所得,你我他都會用的技術、都能解決的問題,那絕成不了你的核心競爭力,天然在團隊內就難成發光體。數組
原則:均選當前最新版本(忽略小版本)網絡
2.11.0
5.2.6.RELEASE
Spring Boot版本:2.3.0.RELEASE
數據結構
說明:相似2.11.0和2.11.x這種小版本號的差別,你權可認爲沒有區別
鑑因而首次展現工程示例代碼,將基本結構展現以下:
app
所有源碼地址在本系列的 最後一篇文章中會所有公示出來
Jackson提供了一種對性能有極致要求的方式:流式API。它用於對性能有極致要求的場景,這個時候就可使用此種方式來對JSON進行讀寫。
關於增量模式和Token概念,在Spirng的 SpEL表達式中也有一樣的概念,這在Spring相關專欄裏你將會再次體會到
本文將看看它是如何寫JSON數據的,也就是JsonGenerator
。
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是個抽象類,它的繼承體系以下:
WriterBasedJsonGenerator
:基於java.io.Writer處理字符編碼(話外音:使用Writer輸出JSON)
SegmentedStringWriter/UTF8Writer
來簡化操做UTF8JsonGenerator
:基於OutputStream + UTF-8處理字符編碼(話外音:明確指定了使用UTF-8編碼把字節變爲字符)默認狀況下(不指定編碼),Jackson默認會使用UTF-8進行編碼,也就是說會使用UTF8JsonGenerator
做爲實際的JSON生成器實現類,具體邏輯將在講述JsonFactory
章節中有所體現,敬請關注。
值得注意的是,抽象基類JsonGenerator
它只負責JSON的生成,至於把生成好的JSON寫到哪裏去它並不關心。好比示例中我給寫到了控制檯,固然你也能夠寫到文件、寫到網絡等等。
Spring MVC中的JSON消息轉換器就是向
HttpOutputMessage
(網絡輸出流)裏寫JSON數據
JsonGenerator
雖然僅是抽象基類,但Jackson它建議咱們使用JsonFactory
工廠來建立其實例,並不須要使用者去關心其底層實現類,所以咱們僅須要面向此抽象類編程便可,此爲對使用者很是友好的設計。
對於JSON生成器來講,寫方法天然是它的靈魂所在。衆所周知,JSON屬於K-V數據結構,所以針對於一個JSON來講,每一段都k額分爲寫key和寫value兩大階段。
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的狀況。
咱們知道在Java中數據存在的形式(類型)很是之多,好比String、int、Reader、char[]...,而在JSON中值的類型只能是以下形式:
{ "name":"YourBatman" }
){ "age":18 }
){ "person":{ "name":"YourBatman", "age":18}}
){"names":[ "YourBatman", "A哥" ]}
){ "success":true }
){ "name":null }
)小貼士:像數組、對象等這些「高級」類型能夠互相無限嵌套
很明顯,Java中的數據類型和JSON中的值類型並非一一對應的關係,那麼這就須要JsonGenerator
在寫入時起到一個橋樑(適配)做用:
下面針對不一樣的Value類型分別做出API講解,給出示例說明。在此以前,請先記住兩個結論,會更有利於你理解示例:
可把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"}
參考上例,不解釋。
@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(); // 一、寫字符串 jsonGenerator.writeString("YourBatman"); // 二、寫對象 jsonGenerator.writeStartObject(); jsonGenerator.writeStringField("enName", "YourBatman"); jsonGenerator.writeEndObject(); // 三、寫數字 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]}
比較簡單,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}
在寫每一個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}
😄這就是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(); } ... }
書寫測試用例:
@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模塊的樹模型沒有意義,因此這裏先賣個關子,保持好奇心哈😄
國人很喜歡把Jackson的序列化(寫JSON)效率和Fastjson進行對比,那麼你敢使用本文的流式API和Fastjson比嗎?結果你猜一下呢?
本文介紹了jackson-core模塊的流式API,以及JsonGenerator寫JSON的使用,相信對你理解Jackson生成JSON方面是有幫助的。它做爲JSON處理的基石,雖然並不推薦直接使用,但僅僅是應用開發級別不推薦哦,若是你是個框架、中間件開發者,這些原理你極可能繞不過。
仍是那句話,本文介紹它的目的並非建議你們去項目上使用,而是爲了後面理解ObjectMapper
夯實基礎,畢竟作技術的要知其然,知其因此然了後,面對問題才能坦然。
Author | A哥(YourBatman) |
---|---|
我的站點 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活躍平臺 |
|
公衆號 | BAT的烏托邦(ID:BAT-utopia) |
知識星球 | BAT的烏托邦 |
每日文章推薦 | 每日文章推薦 |