每棵大樹,都曾只是一粒種子。本文已被 https://www.yourbatman.cn 收錄,裏面一併有Spring技術棧、MyBatis、JVM、中間件等小而美的 專欄供以避免費學習。關注公衆號【 BAT的烏托邦】逐個擊破,深刻掌握,拒絕淺嘗輒止。
你好,我是YourBatman。javascript
上篇文章 體驗了一把ObjectMapper在數據綁定方面的應用,用起來仍是蠻方便的有木有,爲啥很多人說它難用呢,着實費解。我羣裏問了問,主要緣由是它不是靜態方法調用,而且方法名取得不那麼見名之意......java
雖然ObjectMapper
在數據綁定上既能夠處理簡單類型(如Integer、List、Map等),也能處理徹底類型(如POJO),看似無所不能。可是,如有以下場景它依舊不太好實現:node
爲了解決這些問題,Jackson提供了強大的樹模型 API供以使用,這也就是本文的主要的內容。python
小貼士:樹模型雖然是jackson-core模塊裏定義的,可是是由jackson-databind高級模塊提供的實現
2.11.0
5.2.6.RELEASE
2.3.0.RELEASE
樹模型可能比數據綁定更方便,更靈活。特別是在結構高度動態或者不能很好地映射到Java類的狀況下,它就顯得更有價值了。git
樹模型是JSON數據內存樹的表示形式,這是最靈活的方法,它就相似於XML的DOM解析器。Jackson提供了樹模型API來生成和解析 JSON串,主要用到以下三個核心類:github
JsonNodeFactory
:顧名思義,用來構造各類JsonNode節點的工廠。例如對象節點ObjectNode、數組節點ArrayNode等等JsonNode
:表示json節點。能夠往裏面塞值,從而最終構造出一顆json樹ObjectMapper
:實現JsonNode和JSON字符串的互轉這裏有個萌新的概念:JsonNode。它貫穿於整個樹模型中,因此有必要先來認識它。json
JSON節點,可類比XML的DOM樹節點結構來輔助理解。JsonNode是全部JSON節點的基類,它是一個抽象類,它有一個較大的特色:絕大多數的get方法均放在了此抽象類裏(即便它沒有實現),目的是:在不進行類型強制轉換的狀況下遍歷結構。可是,大多數的修改方法都必須經過特定的子類類型去調用,這實際上是合理的。由於在構建/修改某個Node節點時,類型類型信息通常是明確的,而在讀取Node節點時大多數時候並不 太關心節點類型。segmentfault
多個JsonNode節點構成Jackson實現的JSON樹模型的基礎,它是流式API中com.fasterxml.jackson.core.TreeNode
接口的實現,同時它還實現了Iterable
迭代器接口。數組
public abstract class JsonNode extends JsonSerializable.Base implements TreeNode, Iterable<JsonNode> { ... }
JsonNode的繼承圖譜以下(部分):
一目瞭然了吧,基本上每一個數據類型都會有一個JsonNode的實現類型對應。譬如數組節點ArrayNode
、數字節點NumericNode
等等。數據結構
通常狀況下,咱們並不須要經過new關鍵字去構建一個JsonNode實例,而是藉助JsonNodeFactory
工廠來作。
構建JsonNode工廠類。話很少說,用幾個例子跑一跑。
此類節點均爲ValueNode
的子類,特色是:一個節點表示一個值。
@Test public void test1() { JsonNodeFactory factory = JsonNodeFactory.instance; System.out.println("------ValueNode值節點示例------"); // 數字節點 JsonNode node = factory.numberNode(1); System.out.println(node.isNumber() + ":" + node.intValue()); // null節點 node = factory.nullNode(); System.out.println(node.isNull() + ":" + node.asText()); // missing節點 node = factory.missingNode(); System.out.println(node.isMissingNode() + "_" + node.asText()); // POJONode節點 node = factory.pojoNode(new Person("YourBatman", 18)); System.out.println(node.isPojo() + ":" + node.asText()); System.out.println("---" + node.isValueNode() + "---"); }
運行程序,輸出:
------ValueNode值節點示例------ true:1 true:null true_ true:Person(name=YourBatman, age=18) ---true---
此類節點均爲ContainerNode
的子類,特色是:本節點表明一個容器,裏面能夠裝任何其它節點。
Java中容器有兩種:Map和Collection。對應的Jackson也提供了兩種容器節點用於表述此類數據結構:
ObjectNode
:類比Map,採用K-V結構存儲。好比一個JSON結構,根節點 就是一個ObjectNodeArrayNode
:類比Collection、數組。裏面能夠放置任何節點下面用示例感覺一下它們的使用:
@Test public void test2() { JsonNodeFactory factory = JsonNodeFactory.instance; System.out.println("------構建一個JSON結構數據------"); ObjectNode rootNode = factory.objectNode(); // 添加普通值節點 rootNode.put("zhName", "A哥"); // 效果徹底同:rootNode.set("zhName", factory.textNode("A哥")) rootNode.put("enName", "YourBatman"); rootNode.put("age", 18); // 添加數組容器節點 ArrayNode arrayNode = factory.arrayNode(); arrayNode.add("java") .add("javascript") .add("python"); rootNode.set("languages", arrayNode); // 添加對象節點 ObjectNode dogNode = factory.objectNode(); dogNode.put("name", "大黃") .put("age", 3); rootNode.set("dog", dogNode); System.out.println(rootNode); System.out.println(rootNode.get("dog").get("name")); }
運行程序,輸出:
------構建一個JSON結構數據------ {"zhName":"A哥","enName":"YourBatman","age":18,"languages":["java","javascript","python"],"dog":{"name":"大黃","age":3}} "大黃"
樹模型實際上是底層流式API所提出和支持的,典型API即是com.fasterxml.jackson.core.TreeNode
。但經過前面文章的示例講解能夠知道:底層流式API僅定義了接口而並未提供任何實現,甚至半成品都算不上。因此說要使用Jackson的樹模型還得看ObjectMapper,它提供了TreeNode等API的完整實現。
不乏不少小夥伴對ObjectMapper
的樹模型是隻知其一;不知其二的,甚至歷來都沒有用過,其實它是很是靈活和強大的。有了上面的基礎示例作支撐,再來了解它的實現就駕輕就熟多了。
ObjectMapper中提供了樹模型(tree model) API 來生成和解析 json 字符串。若是你不想爲你的 json 結構單獨建類與之對應的話,則能夠選擇該 API,以下圖所示:
ObjectMapper在讀取JSON後提供指向樹的根節點的指針, 根節點可用於遍歷完整的樹。 一樣的,咱們可從讀(反序列化)、寫(序列化)兩個方面來展開。
將Object寫爲JsonNode,ObjectMapper給咱們提供了三個實用API倆操做它:
該方法屬相對較爲經常使用:將任意對象(包括null)寫爲一個JsonNode樹模型。功能上相似於先將Object序列化爲JSON串,再讀爲JsonNode,但很明顯這樣一步到位更加高效。
小貼士:高效不表明性能高,由於其內部實現好仍是調用了
readTree()
方法的
@Test public void test1() { ObjectMapper mapper = new ObjectMapper(); Person person = new Person(); person.setName("YourBatman"); person.setAge(18); person.setDog(new Person.Dog("旺財", 3)); JsonNode node = mapper.valueToTree(person); System.out.println(person); // 遍歷打印全部屬性 Iterator<JsonNode> it = node.iterator(); while (it.hasNext()) { JsonNode nextNode = it.next(); if (nextNode.isContainerNode()) { if (nextNode.isObject()) { System.out.println("狗的屬性:::"); System.out.println(nextNode.get("name")); System.out.println(nextNode.get("age")); } } else { System.out.println(nextNode.asText()); } } // 直接獲取 System.out.println("---------------------------------------"); System.out.println(node.get("dog").get("name")); System.out.println(node.get("dog").get("age")); }
運行程序,控制檯輸出:
Person(name=YourBatman, age=18, dog=Person.Dog(name=旺財, age=3)) YourBatman 18 狗的屬性::: "旺財" 3 --------------------------------------- "旺財" 3
對於JsonNode在這裏補充一個要點:讀取其屬性,你既能夠用迭代器遍歷,也能夠根據key(屬性)直接獲取,是否是和Map的使用幾乎一毛同樣?
顧名思義:將一個JsonNode使用JsonGenerator寫到輸出流裏,此方法直接使用到了JsonGenerator這個API,靈活度槓槓的,但相對偏底層,本處仍舊給個示例玩玩吧(底層API更多詳解,請參見本系列前面幾篇文章):
@Test public void test2() throws IOException { ObjectMapper mapper = new ObjectMapper(); JsonFactory factory = new JsonFactory(); try (JsonGenerator jsonGenerator = factory.createGenerator(System.err, JsonEncoding.UTF8)) { // 一、獲得一個jsonNode(爲了方便我直接用上面API生成了哈) Person person = new Person(); person.setName("YourBatman"); person.setAge(18); JsonNode jsonNode = mapper.valueToTree(person); // 使用JsonGenerator寫到輸出流 mapper.writeTree(jsonGenerator, jsonNode); } }
運行程序,控制檯輸出:
{"name":"YourBatman","age":18,"dog":null}
JsonNode是TreeNode的實現類,上面方法已經給出了使用示例,因此本方法不在贅述你應該不會有意見了吧。
將一個資源(如字符串)讀取爲一個JsonNode樹模型。
這是典型的方法重載設計,API更加友好,全部方法底層均爲_readTreeAndClose()
這個protected方法,可謂「萬劍歸宗」。
下面以最爲常見的:讀取JSON字符串爲例,其它的觸類旁通便可。
@Test public void test3() throws IOException { ObjectMapper mapper = new ObjectMapper(); String jsonStr = "{\"name\":\"YourBatman\",\"age\":18,\"dog\":null}"; // 直接映射爲一個實體對象 // mapper.readValue(jsonStr, Person.class); // 讀取爲一個樹模型 JsonNode node = mapper.readTree(jsonStr); // ... 略 }
至於底層_readTreeAndClose(JsonParser)
方法的具體實現,就有得撈了。不過鑑於它過於枯燥和稍有些燒腦,後面撰有專文詳解,有興趣可持續關注。
理論和示例講完了,光說不練假把式,下面A哥根據經驗,舉兩個樹模型的實際使用示例供你參考。
這種場景其實還蠻常見的,好比有個很經典的場景即是在MQ消費中:生產者通常會巴不得把它能吐出來的屬性儘量都扔出來,但對於不一樣的消費者而言它們的所需每每是不同的:
譬如,生產者生產的消息JSON串以下(模擬數據,總之你就當作它屬性不少、嵌套很深就對了):
{"name":"YourBatman","age":18,"dog":{"name":"旺財","color":"WHITE"},"hobbies":["籃球","football"]}
這時候,我僅關心狗的顏色,腫麼辦呢?相信你已經想到了:樹模型
@Test public void test4() throws IOException { ObjectMapper mapper = new ObjectMapper(); String jsonStr = "{\"name\":\"YourBatman\",\"age\":18,\"dog\":{\"name\":\"旺財\",\"color\":\"WHITE\"},\"hobbies\":[\"籃球\",\"football\"]}"; JsonNode node = mapper.readTree(jsonStr); System.out.println(node.get("dog").get("color").asText()); }
運行程序,控制檯輸出:WHITE
,目標達成。值得注意的是:若是node.get("dog")
沒有這個節點(或者值爲null),是會拋出NPE
異常的,所以請你本身保證代碼的健壯性。
當你不想建立一個Java Bean與JSON屬性相對應時,樹模型的所見即所得特性就很好解決了這個問題。
當數據結構高度動態化(隨時可能新增、刪除節點)時,使用樹模型去處理是一個較好的方案(穩定以後再轉爲Java Bean便可)。這主要是利用了樹模型它具備動態可擴展的特性,知足咱們日益變化的結構:
@Test public void test5() throws JsonProcessingException { String jsonStr = "{\"name\":\"YourBatman\",\"age\":18}"; JsonNode node = new ObjectMapper().readTree(jsonStr); System.out.println("-------------向結構裏動態添加節點------------"); // 動態添加一個myDiy節點,而且該節點仍是ObjectNode節點 ((ObjectNode) node).with("myDiy").put("contry", "China"); System.out.println(node); }
運行程序,控制檯輸出:
-------------向結構裏動態添加節點------------ {"name":"YourBatman","age":18,"myDiy":{"contry":"China"}}
說白了,也沒啥特殊的。拿到一個JsonNode
後你能夠任意的造它,就像Map<Object,Object>
同樣~
樹模型(tree model) API比Jackson 流式(Streaming) API 簡單了不少,不論是生成 json字符串仍是解析json字符串。可是相對於自動化的數據綁定而言仍是比較複雜的。
樹模型(tree model) API在只須要取出一個大json串中的幾個值時比較方便。若是json中每一個(大部分)值都須要得到,那麼這種方式便顯得比較繁瑣了。所以在實際應用中具體問題具體分析,可是,Jackson的樹模型你必須得掌握。
Author | A哥(YourBatman) |
---|---|
我的站點 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活躍平臺 |
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
公衆號 | BAT的烏托邦(ID:BAT-utopia) |
知識星球 | BAT的烏托邦 |
每日文章推薦 | 每日文章推薦 |