一滴水,用顯微鏡看,也是一個大世界。本文已被 https://www.yourbatman.cn 收錄,裏面一併有Spring技術棧、MyBatis、JVM、中間件等小而美的 專欄供以避免費學習。關注公衆號【 BAT的烏托邦】逐個擊破,深刻掌握,拒絕淺嘗輒止。
各位好,我是YourBatman。從本文起,終於要和Jackson的「高級」部分打交道了,也就是數據綁定jackson-databind
模塊。經過接觸它的高級API,你會持續的發現,前面花那麼多篇幅講的core核心部分是價值連城的。畢竟村上春樹也告訴過咱們:人生沒有無用的經歷嘛。java
jackson-databind
包含用於Jackson數據處理器的通用 數據綁定功能和樹模型。它構建在Streaming API之上,並使用Jackson註解
進行配置。它就是Jackson提供的高層API,是開發者使用得最多的方式,所以重要程度可見一斑。git
雖然Jackson最初的用例是JSON數據綁定,但如今它也能夠用於其它數據格式,只要存在解析器和生成器實現便可。但須要注意的是:類的命名在不少地方仍舊使用了「JSON」這個詞(好比JsonGenerator),儘管它與JSON格式沒有實際的硬依賴關係。github
小貼士:底層流式API使用的I/O進行輸入輸出,所以理論上是支持任何格式的
2.11.0
5.2.6.RELEASE
2.3.0.RELEASE
從本文開始,新增導包:json
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>
Tips:jackson-databind
模塊它強依賴於jackson-core和jackson-annotations,只須要導入此包,另外兩個它自動會幫帶進來。segmentfault
這裏須要說明幾句:咱們知道core包中還有個jackson-annotations
,難道不講了嗎?其實不是,是由於單獨講jackson-annotations
並沒有意義,畢竟註解還得靠數據綁定模塊來解析,因此先搞定這個後再殺回去。
據我瞭解,不少小夥伴對Jackson的瞭解起源於ObjectMapper
,止於ObjectMapper
。那行,做爲接觸它的第一篇文章我們就輕鬆點,以應用爲主來總體的認識它。數組
ObjectMapper是jackson-databind模塊最爲重要的一個類,它完成了coder對數據綁定的幾乎全部功能。它是面向用戶的高層API,底層依賴於Streaming API來實現讀/寫。ObjectMapper主要提供的功能點以下:安全
它提供讀取和寫入JSON的功能(最重要的功能)app
它能夠被高度定製,以使用不一樣風格的JSON內容函數
com.fasterxml.jackson.databind.Module
模塊來擴展/豐富功能它還充當了更爲高級(更強大)的API:ObjectReader和ObjectWriter的工廠post
ObjectReader
和ObjectWriter
底層亦是依賴於Streaming API實現讀寫儘管絕大部分的讀/寫API都經過ObjectMapper暴露出去了,但有些功能函數仍是隻放在了ObjectReader/ObjectWriter裏,好比對於讀/寫 長序列 的能力你只能經過ObjectReader#readValues(InputStream) / ObjectWriter#writeValues(OutputStream)
去處理,這是設計者有意爲之,畢竟這種case不多不多,不必和經常使用的湊合在一塊兒嘛。
數據綁定分爲簡單數據綁定和徹底數據綁定:
@Test public void test1() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); // 綁定簡單類型 和 Map類型 Integer age = objectMapper.readValue("1", int.class); Map map = objectMapper.readValue("{\"name\": \"YourBatman\"}", Map.class); System.out.println(age); System.out.println(map); }
運行程序,輸出:
1 {name=YourBatman}
準備一個POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class Person { private String name; private Integer age; }
綁定數據到POJO:
@Test public void test2() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); Person person = objectMapper.readValue("{\"name\": \"YourBatman\", \"age\": 18}", Person.class); System.out.println(person); }
運行程序,輸出:
Person(name=YourBatman, age=18)
在應用及開發中,ObjectMapper絕對是最常使用的,也是你使用Jackson的入口,本文就列列它的那些使用場景。
小貼士:樹模型會單獨成文介紹,體現出它的重要性
提供writeValue()
系列方法用於寫數據(可寫任何類型),也就是咱們常說的序列化。
byte[]
@Test public void test3() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); System.out.println("----------寫簡單類型----------"); System.out.println(objectMapper.writeValueAsString(18)); System.out.println(objectMapper.writeValueAsString("YourBatman")); System.out.println("----------寫集合類型----------"); System.out.println(objectMapper.writeValueAsString(Arrays.asList(1, 2, 3))); System.out.println(objectMapper.writeValueAsString(new HashMap<String, String>() {{ put("zhName", "A哥"); put("enName", "YourBatman"); }})); System.out.println("----------寫POJO----------"); System.out.println(objectMapper.writeValueAsString(new Person("A哥", 18))); }
運行程序,輸出:
----------寫簡單類型---------- 18 "YourBatman" ----------寫集合類型---------- [1,2,3] {"zhName":"A哥","enName":"YourBatman"} ----------寫POJO---------- {"name":"A哥","age":18}
提供readValue()
系列方法用於讀數據(通常讀字符串類型),也就是咱們常說的反序列化。
readValue(String content, Class<T> valueType)
:讀爲指定class類型的對象,此方法最經常使用readValue(String content, TypeReference<T> valueTypeRef)
:T表示泛型類型,如List<T>
這種類型,通常用於集合/Map的反序列化@Test public void test4() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); System.out.println("----------讀簡單類型----------"); System.out.println(objectMapper.readValue("18", Integer.class)); // 拋錯:JsonParseException 單獨的一個串,解析會拋錯 // System.out.println(objectMapper.readValue("YourBatman", String.class)); System.out.println("----------讀集合類型----------"); System.out.println(objectMapper.readValue("[1,2,3]", List.class)); System.out.println(objectMapper.readValue("{\"zhName\":\"A哥\",\"enName\":\"YourBatman\"}", Map.class)); System.out.println("----------讀POJO----------"); System.out.println(objectMapper.readValue("{\"name\":\"A哥\",\"age\":18}", Person.class)); }
運行程序,輸出:
----------讀簡單類型---------- 18 ----------讀集合類型---------- [1, 2, 3] {zhName=A哥, enName=YourBatman} ----------讀POJO---------- Person(name=A哥, age=18)
不一樣於序列化,能夠把「全部」寫成爲一個字符串。反序列化場景有它特殊的地方,好比例子中所示:不能反序列化一個「單純的」字符串。
從例舉出來的三個read讀方法中,就應該以爲事情還沒完,好比這個帶泛型的case:
@Test public void test5() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); System.out.println("----------讀集合類型----------"); List<Long> list = objectMapper.readValue("[1,2,3]", List.class); Long id = list.get(0); System.out.println(id); }
運行程序,拋錯:
----------讀集合類型---------- java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long at cn.yourbatman.jackson.core.ObjectMapperDemo.test5(ObjectMapperDemo.java:100) ...
異常棧裏指出:Long id = list.get(0);
這一句出現了類型轉換異常,這即是問題緣由所在:泛型擦除,參考圖示以下(明明泛型類型是Long,但實際裝的是Integer類型):
對這種問題,你可能會「動腦筋」思考:寫成[1L,2L,3L]
這樣行不行。思想很活躍,奈何現實依舊殘酷,運行拋錯:
com.fasterxml.jackson.core.JsonParseException: Unexpected character ('L' (code 76)): was expecting comma to separate Array entries at [Source: (String)"[1L,2L,3L]"; line: 1, column: 4] ...
這是典型的泛型擦除問題。該問題只可能出如今讀(反序列化)上,不能出如今寫上。那麼這種問題怎麼破?
在解決此問題以前,咱們得先對Java中的泛型擦除有所瞭解,至少知道以下兩點結論:
此問題在開發過程當中很是高頻,有了此理論做爲支撐,A哥提供兩種能夠解決本問題的方案供以參考:
理論依據:成員變量的泛型類型不會被擦除
@Test public void test6() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); System.out.println("----------讀集合類型----------"); Data data = objectMapper.readValue("{\"ids\" : [1,2,3]}", Data.class); Long id = data.getIds().get(0); System.out.println(id); } @lombok.Data private static class Data { private List<Long> ids; }
運行程序,一切正常:
----------讀集合類型---------- 1
TypeReference<T>
官方早早就爲咱們考慮好了這類泛型擦除的問題,因此它提供了TypeReference<T>
方便咱們把泛型類型保留下來,使用起來是很是的方便的:
@Test public void test7() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); System.out.println("----------讀集合類型----------"); List<Long> ids = objectMapper.readValue("[1,2,3]", new TypeReference<List<Long>>() { }); Long id = ids.get(0); System.out.println(id); }
運行程序,一切正常:
----------讀集合類型---------- 1
本方案的理論依據是:泛型接口/類上的泛型類型不會被擦除。
對於泛型擦除狀況,解決思路是hold住泛型類型,這樣反序列化的時候纔不會抓瞎。但凡只要一抓瞎,Jackson就木有辦法只能採用通用/默認類型去裝載嘍。
自2.10
版本起,給ObjectMapper提供了一個子類:JsonMapper
,使得語義更加明確,專門用於處理JSON格式。
嚴格意義上講,ObjectMapper不侷限於處理JSON格式,好比後面會講到的它的另一個子類
YAMLMapper
用於對Yaml格式的支持(需額外導包,後面見~)
另外,因爲構建一個ObjectMapper實例屬於高頻動做,所以Jackson也順應潮流的提供了MapperBuilder
構建器(2.10版本起)。咱們能夠經過此構建起很容易的獲得一個ObjectMapper(以JsonMapper爲例)實例來使用:
@Test public void test8() throws JsonProcessingException { JsonMapper jsonMapper = JsonMapper.builder() .configure(JsonReadFeature.ALLOW_SINGLE_QUOTES, true) .build(); Person person = jsonMapper.readValue("{'name': 'YourBatman', 'age': 18}", Person.class); System.out.println(person); }
運行程序,正常輸出:
Person(name=YourBatman, age=18)
本文內容很輕鬆,講述了ObjectMapper的平常使用,使用它進行讀/寫,完成平常功能。
對於寫來講比較簡單,一個writeValueAsString(obj)
方法走天下;但對於讀來講,除了使用readValue(String content, Class<T> valueType)
自動完成數據綁定外,須要特別注意泛型擦除問題:若反序列化成爲一個集合類型(Collection or Map),泛型會被擦除,此時你應該使用readValue(String content, TypeReference<T> valueTypeRef)
方法代替。
小貼士:若你在工程中遇到
objectMapper.readValue(xxx, List.class)
這種代碼,那確定是有安全隱患的(但不必定報錯)
Author | A哥(YourBatman) |
---|---|
我的站點 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活躍平臺 |
|
公衆號 | BAT的烏托邦(ID:BAT-utopia) |
知識星球 | BAT的烏托邦 |
每日文章推薦 | 每日文章推薦 |