爱爱爱爱电影会员账号怎么登录_亚洲欧美h片在线看_狠狠综合久久久久综合网_欧美精品成人久久久久_最美情侣高清视频播放

您當(dāng)前的位置:首頁(yè)> 商業(yè)資訊 >正文
環(huán)球訊息:[Gson]Gson 解析 Json 容錯(cuò)策略

2023-03-31 08:22:47     來(lái)源 : 騰訊云

一. 序章

文章評(píng)論里后臺(tái)有一些小伙伴,針對(duì)具體數(shù)據(jù)容錯(cuò)的場(chǎng)景,提出了具體的問(wèn)題。今天就在這篇文章里統(tǒng)一解答,并且給出解決方案。


【資料圖】

二. GSON 數(shù)據(jù)容錯(cuò)實(shí)例

就像前文中介紹的一樣,GSON 已經(jīng)提供了一些簡(jiǎn)單的注解,去做數(shù)據(jù)的容錯(cuò)處理。更復(fù)雜的操作,就需要用到 TypeAdapter 了,需要注意的是,一旦上了 TypeAdapter 之后,注解的配置就會(huì)失效。

2.1 什么是 TypeAdapter

TypeAdapter 是 GSON 2.1 版本開(kāi)始支持的一個(gè)抽象類,用于接管某些類型的序列化和反序列化。TypeAdapter 最重要的兩個(gè)方法就是 write()read(),它們分別接管了序列化和反序列化的具體過(guò)程。

如果想單獨(dú)接管序列化或反序列化的某一個(gè)過(guò)程,可以使用 JsonSerializer 和 JsonDeserializer 這兩個(gè)接口,它們組合起來(lái)的效果和 TypeAdapter 類似,但是其內(nèi)部實(shí)現(xiàn)是不同的。

簡(jiǎn)單來(lái)說(shuō),TypeAdapter 是支持流的,所以它比較省內(nèi)存,但是使用起來(lái)有些不方便。而 JsonSerializer 和 JsonDeserializer 是將數(shù)據(jù)都讀到內(nèi)存中再進(jìn)行操作,會(huì)比 TypeAdapter 更費(fèi)內(nèi)存,但是 API 使用起來(lái)更清晰一些。

雖然 TypeAdapter 更省內(nèi)存,但是通常我們業(yè)務(wù)接口所使用的那點(diǎn)數(shù)據(jù)量,所占用的內(nèi)存其實(shí)影響不大,可以忽略不計(jì)。

因?yàn)?TypeAdapter、JsonSerializer 以及 JsonDeserializer 都需要配合 GsonBuilder.registerTypeAdapter()方法,所以在本文中,此種接管方式,統(tǒng)稱為 TypeAdapter 接管。

2.2 空字符串轉(zhuǎn) 0

對(duì)于一些強(qiáng)轉(zhuǎn)有效的類型轉(zhuǎn)換,GSON 本身是有一些默認(rèn)的容錯(cuò)機(jī)制的。比如:將字符串 “18” 轉(zhuǎn)換成 Java 中整型的 18,這是被默認(rèn)支持的。

例如我有一個(gè)記錄用戶信息的 User 類。

class User{    var name = ""    var age = 0    override fun toString(): String {        return """            {                "name":"${name}",                "age":${age}            }        """.trimIndent()    }}

User 類中包含 nameage兩個(gè)字段,其中 age對(duì)應(yīng)的 JSON 類型,可以是 18也可以是 "18",這都是允許的。

{"name":"承香墨影","age":18 // "age":"18"}

那假如服務(wù)端說(shuō),這個(gè)用戶沒(méi)有填年齡的信息,所以直接返回了一個(gè)空串 "",那這個(gè)時(shí)候客戶端用 Gson 解析就悲劇了。

這當(dāng)然是服務(wù)端的問(wèn)題,如果數(shù)據(jù)明確為 Int 類型,那么就算是默認(rèn)值也應(yīng)該是 0 或者 -1。

但遇到這樣的情況,你還用默認(rèn)的 GSON 策略去解析,你將得到一個(gè) Crash。

Caused by: com.google.gson.JsonSyntaxException: - java.lang.NumberFormatException: --empty String

沒(méi)有一點(diǎn)意外也沒(méi)有一點(diǎn)驚喜的 Crash 了,那接下來(lái)看看如何解決這樣的數(shù)據(jù)容錯(cuò)問(wèn)題?

因?yàn)檫@里的場(chǎng)景中,只需要反序列化的操作,所以我們實(shí)現(xiàn) JsonDeserializer 接口即可,接管的是 Int 類型。直接上例子吧。

class IntDefaut0Adapter : JsonDeserializer {    override fun deserialize(json: JsonElement?,                              typeOfT: Type?,                              context: JsonDeserializationContext?): Int {        if (json?.getAsString().equals("")) {            return 0        }        try {            return json!!.getAsInt()        } catch (e: NumberFormatException) {            return 0        }    }}fun intDefault0(){    val jsonStr = """        {            "name":"承香墨影",            "age":""        }    """.trimIndent()    val user = GsonBuilder()            .registerTypeAdapter(                    Int::class.java,                    IntDefaut0Adapter())            .create()            .fromJson(jsonStr,User::class.java)    Log.i("cxmydev","user: ${user.toString()}")}

在 IntDefaut0Adapter 中,首先判斷數(shù)據(jù)字符串是否為空字符串 "",如果是則直接返回 0,否則將其按 Int 類型解析。在這個(gè)例子中,將整型 0 作為一個(gè)異常參數(shù)進(jìn)行處理。

2.3 null、[]、List 轉(zhuǎn) List

還有一些小伙伴比較關(guān)心的,對(duì)于 JSONObject 和 JSONArray 兼容的問(wèn)題。

例如需要返回一個(gè) List,翻譯成 JSON 數(shù)據(jù)就應(yīng)該是方括號(hào) []包裹的 JSONArray。但是在列表為空的時(shí)候,服務(wù)端返回的數(shù)據(jù),什么情況都有可能。

{"name":"承香墨影","languages":["EN","CN"] // 理想的數(shù)據(jù)// "languages":""http:// "languages":null// "languages":{}}

例子的 JSON 中,languages字段表示當(dāng)前用戶所掌握的語(yǔ)言。當(dāng)語(yǔ)言字段沒(méi)有被設(shè)置的時(shí)候,服務(wù)端返回的數(shù)據(jù)不一致,如何兼容呢?

我們?cè)谠镜?User 類中,增加一個(gè) languages 的字段,類型為 ArrayList。

var languages = ArrayList()

在 Java 中,列表集合都會(huì)實(shí)現(xiàn) List 接口,所以我們?cè)趯?shí)現(xiàn) JsonDeserializer 的時(shí)候,解析攔截的應(yīng)該是 List。

在這個(gè)情況下,可以使用 JsonElement 的 isJsonArray()方法,判斷當(dāng)前是否是一個(gè)合法的 JSONArray 的數(shù)組,一旦不正確,就直接返回一個(gè)空的集合即可。

class ArraySecurityAdapter:JsonDeserializer>{    override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): List<*> {              if(json.isJsonArray()){            val newGson = Gson()            return newGson.fromJson(json, typeOfT)        }else{            return Collections.EMPTY_LIST        }    }}fun listDefaultEmpty(){    val jsonStr = """        {            "name":"承香墨影",            "age":"18",            "languages":{}        }    """.trimIndent()    val user = GsonBuilder()            .registerTypeHierarchyAdapter(                    List::class.java,                    ArraySecurityAdapter())            .create()            .fromJson(jsonStr,User::class.java)    Log.i("cxmydev","user: ${user.toString()}")}

其核心就是 isJsonArray()方法,判斷當(dāng)前是否是一個(gè) JSONArray,如果是,再具體解析即可。到這一步就很靈活了,你可以直接用 Gson 將數(shù)據(jù)反序列化成一個(gè) List,也可以將通過(guò)一個(gè) for 循環(huán)將其中的每一項(xiàng)單獨(dú)反序列化。

需要注意的是,如果依然想用 Gson 來(lái)解析,需要重新創(chuàng)建一個(gè)新的 Gson 對(duì)象,不可以直接復(fù)用 JsonDeserializationContext,否則會(huì)造成遞歸調(diào)用。

另外還有一個(gè)細(xì)節(jié),在這個(gè)例子中,調(diào)用的是 registerTypeHierarchyAdapter()方法來(lái)注冊(cè) TypeAdapter,它和我們前面介紹的 registerTypeAdapter()有什么區(qū)別呢?

通常我們會(huì)根據(jù)不同的場(chǎng)景,選擇不同數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的集合類,例如 ArrayList 或者 LinkedList。但是 registerTypeAdapter()方法,要求我們傳遞一個(gè)明確的類型,也就是說(shuō)它不支持繼承,而 registerTypeHierarchyAdapter()則可以支持繼承。

我們想用 List 來(lái)替代所有的 List 子類,就需要使用 registerTypeHierarchyAdapter()方法,或者我們的 Java Bean 中,只使用 List。這兩種情況都是可以的。

2.4 保留原 Json 字符串

看到這個(gè)小標(biāo)題,可能會(huì)有疑問(wèn),保留原 Json 字符串是一個(gè)什么情況?得到的 Json 數(shù)據(jù),本身就是一個(gè)字符串,且挺我細(xì)細(xì)說(shuō)來(lái)。

舉個(gè)例子,前面定義的 User 類,需要存到 SQLite 數(shù)據(jù)庫(kù)中,語(yǔ)言(languages)字段也是需要存儲(chǔ)的。說(shuō)到 SQLite,當(dāng)然優(yōu)先使用一些開(kāi)源的 ORM 框架了,而不少優(yōu)秀的 ORM-SQLite 框架,都通過(guò)外鍵的形式支持了一對(duì)多的存儲(chǔ)。例如一篇文章對(duì)應(yīng)多條評(píng)論,一條用戶信息對(duì)應(yīng)對(duì)應(yīng)多條語(yǔ)言信息。

這種場(chǎng)景下我們當(dāng)然可以使用 ORM 框架本身提供的一對(duì)多的存儲(chǔ)形式。但是如果像現(xiàn)在的例子中,只是簡(jiǎn)單的存儲(chǔ)一些有限的數(shù)據(jù),例如用戶會(huì)的語(yǔ)言(languages),這種簡(jiǎn)單的有限數(shù)據(jù),用外鍵有一些偏重了。

此時(shí)我們就想,要是可以直接在 SQLite 中存儲(chǔ) languages 字段的 JSON,將其當(dāng)成一個(gè)字符串去存儲(chǔ),是不是就簡(jiǎn)單了?把一個(gè)多級(jí)的結(jié)構(gòu)拉平成一級(jí),剩下的只需要擴(kuò)展出一個(gè)反序列化的方法,對(duì)業(yè)務(wù)來(lái)說(shuō),這些操作都是透明的。

那拍腦袋想,如果 Gson 有簡(jiǎn)單的容錯(cuò),那我們將這個(gè)解析的字段類型定義成 String,是不是就可以做到了?

@SerializedName("languages")var languageStr = ""

很遺憾,這并沒(méi)有辦法做到,如果你這樣使用,你將得到一個(gè) IllegalStateException 的異常。

Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_ARRAY at line 4 column 18 path $.languages

之所以會(huì)出現(xiàn)這樣的情況,簡(jiǎn)單來(lái)說(shuō),雖然 deserialize()方法傳遞的參數(shù)都是 JsonElement,但是 JsonElement 只是一個(gè)抽象類,最終會(huì)根據(jù)數(shù)據(jù)的情況,轉(zhuǎn)換成它的幾個(gè)實(shí)現(xiàn)類的其中之一,這些實(shí)現(xiàn)類都是 final class,分別是 JsonObject、JsonArray、JsonPrimitive、JsonNull,這些從命名上就很好理解了,它們代表了不通的 JSON 數(shù)據(jù)場(chǎng)景,就不過(guò)多介紹了。

使用了 Gson 之后,遇到花括號(hào) {}會(huì)生成一個(gè) JsonObject,而字符串則是基本類型的 JsonPrimitive 對(duì)象,它們?cè)?Gson 內(nèi)部的解析流程是不一樣的,這就造成了 IllegalStateException 異常。

那么接下來(lái)看看如何解決這個(gè)問(wèn)題。

既然 TypeAdapter 是 Gson 解析的銀彈,找不到解決方案,用它就對(duì)了。思路繼續(xù)是用 JsonDeserializer 來(lái)接管解析,這一次將 User 類的整個(gè)解析都接管了。

class UserGsonAdapter:JsonDeserializer{    override fun deserialize(json: JsonElement,                              typeOfT: Type?,                              context: JsonDeserializationContext?): User {                var user = User()        if(json.isJsonObject){            val jsonObject = JSONObject(json.asJsonObject.toString())            user.name = jsonObject.optString("name")            user.age = jsonObject.optInt("age")            user.languageStr = jsonObject.optString("languages")            user.languages = ArrayList()            val languageJsonArray = JSONArray(user.languageStr)            for(i in 0 until languageJsonArray.length()){                user.languages.add(languageJsonArray.optString(i))            }        }        return user    }}fun userGsonStr(){    val jsonStr = """        {            "name":"承香墨影",            "age":"18",            "languages":["CN","EN"]        }    """.trimIndent()    val user = GsonBuilder()            .registerTypeAdapter(                    User::class.java,                    UserGsonAdapter())            .create()            .fromJson(jsonStr,User::class.java)    Log.i("cxmydev","user: \n${user.toString()}")}

在這里我直接使用標(biāo)準(zhǔn) API org.json 包中的類去解析 JSON 數(shù)據(jù),當(dāng)然你也可以通過(guò) Gson 本身提供的一些方法去解析,這里只是提供一個(gè)思路而已。

最終 Log 輸出的效果如下:

{"name":"承香墨影","age":18,"languagesJson":["CN","EN"],"languages size:"2}

在這個(gè)例子中,最終解析還是使用了標(biāo)準(zhǔn)的 JSONObject 和 JSONArray 類,和 Gson 沒(méi)有任何關(guān)系,Gson 只是起到了一個(gè)橋接的作用,好像這個(gè)例子也沒(méi)什么實(shí)際用處。

不談場(chǎng)景說(shuō)應(yīng)用都是耍流氓,那么如果是使用 Retrofit 呢?Retrofit 可以配置 Gson 做為數(shù)據(jù)的轉(zhuǎn)換器,在其內(nèi)部就完成了反序列化的過(guò)程。這種情況,配合 Gson 的 TypeAdapter,就不需要我們?cè)陬~外的編寫(xiě)解析的代碼了,網(wǎng)絡(luò)請(qǐng)求走一套邏輯即可。

如果覺(jué)得在構(gòu)造 Retrofit 的時(shí)候,為 Gson 添加 TypeAdapter 有些入侵嚴(yán)重了,可以配合 @JsonAdapter注解使用。

三. 小結(jié)時(shí)刻

針對(duì)服務(wù)端返回?cái)?shù)據(jù)的容錯(cuò)處理,很大一部分其實(shí)都是來(lái)自雙端沒(méi)有保證數(shù)據(jù)一致的問(wèn)題。而針對(duì)開(kāi)發(fā)者來(lái)說(shuō),要做到外部數(shù)據(jù)均不可信的,客戶端不信本地讀取的數(shù)據(jù)、不信服務(wù)端返回的數(shù)據(jù),服務(wù)端也不能相信客戶端傳遞的數(shù)據(jù)。這就是所謂防御式編程。

言歸正傳,我們小結(jié)一下本文的內(nèi)容:

TypeAdapter(包含JsonSerializer、JsonDeserializer) 是 Gson 解析的銀彈,所有 Json 解析的定制化要求都可以通過(guò)它來(lái)實(shí)現(xiàn)。registerTypeAdapter()方法需要制定確定的數(shù)據(jù)類型,如果想支持繼承,需要使用 registerTypeHierarchyAdapter()方法。如果數(shù)據(jù)量不大,推薦使用 JsonSerializer 和 JsonDeserializer。針對(duì)整個(gè) Java Bean 的解析接管,可以使用 @JsonAdapter注解。

標(biāo)簽:

熱門推薦

精彩放送

X 關(guān)閉

行業(yè)要聞
土巴兔公司持續(xù)盈利能力存疑 毛利率超90%比肩茅臺(tái)凈利率僅10%

土巴兔公司持續(xù)盈利能力存疑 毛利率超90%比肩茅臺(tái)凈利率僅10%

6月底前河南省實(shí)現(xiàn)“場(chǎng)所碼”全覆蓋 升級(jí)后的“場(chǎng)所碼”有啥功能

6月底前河南省實(shí)現(xiàn)“場(chǎng)所碼”全覆蓋 升級(jí)后的“場(chǎng)所碼”有啥功能

公安部推行新車上牌新規(guī) 便利群眾快捷上牌

公安部推行新車上牌新規(guī) 便利群眾快捷上牌

北京市經(jīng)信局公布今年第三批北京市“專精特新”中小企業(yè)名單

北京市經(jīng)信局公布今年第三批北京市“專精特新”中小企業(yè)名單

“520”迎婚姻登記高峰!深圳市民政局:高峰日扎堆登記可能影響體驗(yàn)感

“520”迎婚姻登記高峰!深圳市民政局:高峰日扎堆登記可能影響體驗(yàn)感

北京市東城區(qū)41條措施落地 2790家中小微企業(yè)享受政策支持

北京市東城區(qū)41條措施落地 2790家中小微企業(yè)享受政策支持

地球上最幸福的人!56歲非洲建筑師獲普利茲克建筑獎(jiǎng)

地球上最幸福的人!56歲非洲建筑師獲普利茲克建筑獎(jiǎng)

新疆阿克蘇地區(qū)庫(kù)車市發(fā)生4.1級(jí)地震 震源深度21千米

新疆阿克蘇地區(qū)庫(kù)車市發(fā)生4.1級(jí)地震 震源深度21千米

從東北到西北 他在“軍墾第一城”規(guī)劃著城建未來(lái)

從東北到西北 他在“軍墾第一城”規(guī)劃著城建未來(lái)

西藏米林“家庭農(nóng)場(chǎng)”:引領(lǐng)種植產(chǎn)業(yè) 助力鄉(xiāng)村振興

西藏米林“家庭農(nóng)場(chǎng)”:引領(lǐng)種植產(chǎn)業(yè) 助力鄉(xiāng)村振興