Json反序列化与Java泛型
Java的JSON庫有很多,本文分析google的Gson和alibaba的fastjson,在Java泛型場景反序列化的一些有意思的行為。考慮下面的json字符串:
["2147483648","2147483647" ]用fastjson在不指定類型的情況下解析,下面的代碼輸出啥:
JSON.parseArray(s).forEach(o -> { System.out.println(o.getClass()); });答案是:
class java.lang.Long class java.lang.Integer是不是感覺有點(diǎn)兒奇怪,兩個(gè)都是數(shù)字啊,居然輸出了不同的類型,原因我們下面細(xì)講。再看看Gson, 用Gson解析并且不指定泛型類型的話,下面的代碼輸出啥:
new Gson().fromJson(s, List.class).forEach(o -> { System.out.println(o.getClass()); });答案是:
class java.lang.Double class java.lang.Double這次兩個(gè)都是Double類型,明明是整數(shù)啊,為啥到Gson這里變成Double了?
我們試了兩個(gè)Json庫,解析相同的json字符串,得到全然不同的結(jié)果。如果在實(shí)際使用的時(shí)候,用這種方式反序列化JSON,很容易出現(xiàn)BUG,而且是運(yùn)行時(shí)才可能出現(xiàn),這種問題一旦出現(xiàn)往往很難排查。因此為了保證泛型類型Json反序列化的正確性,一定要明確指定泛型的類型。下面我們先看看正確的解析應(yīng)該怎么寫,再深度探討一下內(nèi)部的原理。
正確的泛型Json反序列化
fastjson和Gson都提供了泛型類型的反序列化方案,先來看看fastjson,對于上面的case,正確的反序列化代碼如下:
JSON.parseObject(s, new TypeReference<List<Long>>(){}) .forEach(o -> { System.out.println(o.getClass()); });創(chuàng)建一個(gè)確定泛型類型的TypeReference子類(這里是匿名內(nèi)部類),將這個(gè)子類傳遞給fastjson以幫助fastjson在運(yùn)行時(shí)獲得泛型的具體類型信息,從而實(shí)現(xiàn)泛型正確反序列化。Gson的方案與fastjson相同,或者應(yīng)該反過來說fastjson的方案與Gson相同。大家感興趣的話可以看看fastjson的TypeReference和Gson的TypeToken代碼,基本上fastjson就是抄襲Gson,連注釋都抄了......。Gson指定泛型類型的反序列化方法如下,也是創(chuàng)建一個(gè)確定泛型類型的匿名子類:
new Gson().<List<Long>>fromJson(s, new TypeToken<List<Long>>(){}.getType()) .forEach(o -> { System.out.println(o.getClass()); });由于fastjson的方案是來自Gson,以下只討論Gson的泛型反序列化原理。為什么泛型的反序列化顯得這么麻煩呢,非要通過子類化的方式,不能直接告訴Gson泛型的類型嗎?是的,Java是真的做不到,其實(shí)理由很簡單,泛型類既然是泛型,意味著就不應(yīng)該帶有具體泛型類型的信息,因此泛型類的字節(jié)碼本身就不應(yīng)該也無法保存泛型類型,但是子類如果繼承一個(gè)明確泛型類型的父類(父類是一個(gè)泛型類型,Gson里面就是TypeToken),子類必須保存父類的明確類型信息,通過Class類的getGenericSuperclass方法能夠獲得父類類型信息,該類型信息包含了父類具體的泛型類型信息。這個(gè)方法幫助Java的泛型體系完整化了,是非常重要的一個(gè)方法。
類庫設(shè)計(jì)分析
面對相同的設(shè)計(jì)問題,fastjson與Gson在很多點(diǎn)上的選擇不同,借此機(jī)會可以一窺類庫設(shè)計(jì)的思想,讓我們一一來看。
是靜態(tài)方法還是實(shí)例化
使用Gson之前,必須進(jìn)行實(shí)例化,Gson提供了兩種方式:一種是無參數(shù)構(gòu)造器,一種是通過GsonBuilder,后者能夠進(jìn)行更多的定制,但無論是哪種方法,都需要實(shí)例化一個(gè)Gson對象。但是Fastjson使用之前是不需要實(shí)例化的,直接使用JSON類的靜態(tài)方法即可實(shí)現(xiàn)json序列化和反序列化。這一點(diǎn)上來講,Fastjson比較方便,雖然Gson是線程安全的,可以用static變量來聲明一個(gè)Gson實(shí)例(餓漢模式的單例)然后全局使用,但是還是比Fastjson多了一步。但是Gson這么做的好處是如果序列化(反序列化)的定制比較多,可以在初始化的時(shí)候完成復(fù)雜的擴(kuò)展定制,使用的時(shí)候依然保持簡單,Fastjson就需要每次都傳遞額外的參數(shù)來實(shí)現(xiàn)。總體來講各有優(yōu)化,Gson是線程安全的,大部分場景都是定義全局的靜態(tài)單例,用起來跟Fastjson差不多。Gson在這里的選擇傾向于希望對外的接口保持一致和簡單,即無論怎么定制邏輯,Json的序列化和反序列化就那么幾個(gè)方法,內(nèi)部邏輯可以定制,但是使用接口不用改變。
數(shù)字的默認(rèn)類型
對于數(shù)字類型,如果沒有明確指定類型,Gson默認(rèn)都解析成Double類型,而Fastjson會根據(jù)數(shù)字的不同,解析成Long、Integer或者BigDecimal。我們在生產(chǎn)中用Fastjson就遇到這種問題:由于集合沒有指定泛型類型,反序列化的時(shí)候,不同大小的數(shù)字被反序列化成了不同的類型,導(dǎo)致業(yè)務(wù)邏輯出錯(cuò)。這種未制定類型情況下,感覺Gson的處理更合適一些,既然未指定類型,對外的默認(rèn)類型始終是Double,接口對外的心智更穩(wěn)定。
集合類型反序列化
對于列表類型的反序列化,Fastjson提供了parseArray系列方法,這樣很多情況下可以避免使用TypeReference,代碼寫起來更簡單。但是Gson就沒有這種方法,如果需要解析列表,必須使用TypeToken<List<Xxx>>,并沒有為列表設(shè)置特殊的方法,這里依然能看到 Gson希望對外的接口保持一致和簡單 ,即便犧牲一點(diǎn)兒方便性。
泛型反序列化
為了解析泛型,Gson和Fastjson都提供了類似的機(jī)制(Gson使用TypeToken承載類型,而Fastjson使用TypeReference承載類型),利用子類繼承確定泛型類型父類的方式,獲得類型,區(qū)別是Gson的接口只接受Type類型的參數(shù),不接受TypeToken參數(shù),這是因?yàn)門ype是JDK的自帶類型,這種設(shè)計(jì)的效果是Gson的接口非常簡單。Fastjson的接口可以支持Type參數(shù),也支持TypeReference參數(shù)。
小結(jié)
整體上能明顯看出來fastjson更多是長出來的,接口多而全,應(yīng)該是不斷有人提需求的結(jié)果,而Gson是設(shè)計(jì)出來的,接口的一致性很強(qiáng),高內(nèi)聚低耦合,有些時(shí)候?qū)幵笭奚涌诘谋憷?#xff0c;也要保證接口對外的一致性、簡單和概念完整,從設(shè)計(jì)上我是崇尚Gson的設(shè)計(jì)理念的,但實(shí)際的開發(fā)過程更容易演變成fastjson的模式,在中國程序員的地位真的不夠高。
參考資料
[1]. fastjson源代碼
[2]. Gson源代碼
[3]. https://github.com/google/gson/blob/master/GsonDesignDocument.md
總結(jié)
以上是生活随笔為你收集整理的Json反序列化与Java泛型的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PXE+Kickstart实现无人值守批
- 下一篇: 将php-fpm添加至service服务