技術簡歷的技能樹這一項中,JSON 和 GSON 都是常客。可是還有面試候選者將他們的理解停留在最簡單的使用上。java
"JSON 是一種具備自描述的、獨立於語言的、輕量級文本數據交換格式,常常被用於數據的存儲和傳輸。而 GSON 能夠幫咱們快速的將 JSON 數據,在對象之間序列化和反序列化。"android
GSON 的 toJson()
和 fromJson()
這兩個方法,是 GSON 最基本的使用方式,它很直觀,也沒什麼好說的。但當被問及 GSON 如何對 JSON 數據容錯,如何靈活序列化和反序列化的時候,就有點抓瞎了。面試
JSON 數據容錯,最簡單的方式是讓先後端數據保持一致,就根本不存在容錯的問題,可是現實場景中,並不如咱們預期的那般美好。json
舉兩個簡單的例子:User 類中的姓名,有些接口返回的 Key 值是 name
,而有些返回的是 username
,如何作容錯呢?再好比 age
字段返回的是如 "18"
這樣的字符串,而 Java 對象將其解析成 Int 類型時,雖然 GSON 有必定的類型容錯性,這樣解析可以成功,可是若是 age
字段的返回值變成了 ""
呢,如何讓其不拋出異常,而且設置爲默認值 0?後端
在本文中,咱們就來詳細看看,GSON 是如何對數據進行容錯解析的。api
GSON 是 Google 官方出的一個 JSON 解析庫,比較常規的使用方式就是用toJson()
將 Java 對象序列化成 JSON 數據,或者用 fromJson()
將 JSON 數據反序列化成 Java 對象。性能優化
// 序列化 val user = User() user.userName = "Android開發架構" user.age = 18 user.gender = 1 val jsonStr = Gson().toJson(user) Log.i("cxmydev","json:$jsonStr") // json:{"age":18,"gender":1,"userName":"Android開發架構"} // 反序列化 val newUser = Gson().fromJson(jsonStr,User::class.java) Log.i("cxmydev","userName:${newUser.userName}") // userName:Android開發架構
GSON 很方便,大部分時候並不須要咱們額外處理什麼,拿來即用。惟一須要注意的可能就是泛型擦除,針對泛型的解析,無非就是參數的差別而已。架構
在數據都很規範的狀況下,使用 GSON 就只涉及到這兩個方法,可是針對一些特殊的場景,就沒那麼簡單了。ide
GSON 提供了註解的方式,來配置最簡單的靈活性,這裏介紹兩個註解 @SerializedName 和 @Expose。性能
@SerializedName 能夠用來配置 JSON 字段的名字,最多見的場景來自不一樣語言的命名方式不統一,有些使用下劃線分割,有些使用駝峯命名法。
仍是拿 User 類來舉例,Java 對象中定義的用戶名稱,使用的是 userName
,而返回的 JSON 數據中,用的是 user_name
,這樣的差別就能夠用 @SerializedName 來解決。
class User{ @SerializedName("user_name") var userName :String? = null var gender = 0 var age = 0 }
而在前文中,針對同一個 User 對象中的用戶名稱,如今不一樣的接口返回有差別,分別爲:name
、user_name
、username
,這種差別也能夠用 @SerializedName 來解決。
在 @SerializedName 中,還有一個 alternate
字段,能夠對同一個字段配置多個解析名稱。
class User{ @SerializedName(value = "user_name",alternate = arrayOf("name","username")) var userName :String? = null var gender = 0 var age = 0 }
再來看看 @Expose,它是用來配置一些例外的字段。
在序列化和反序列化的過程當中,總有一些字段是和本地業務相關的,並不須要從 JSON 中序列化出來,也不須要在傳遞 JSON 數據的時候,將其序列化。
這樣的狀況用 @Expose 就很好解決。從字面上理解,將這個字段暴露出去,就是參與序列化和反序列化。而一旦使用 @Expose,那麼常規的 new Gson()
的方式已經不適用了,須要 GsonBuilder
配合.excludeFieldsWithoutExposeAnnotation()
方法使用。
@Expose 有兩個配置項,分別是 serialize
和 deserialize
,他們用於指定序列化和反序列化是否包含此字段,默認值均爲 True。
class User{ @SerializedName(value = "user_name",alternate = arrayOf("name","username")) @Expose var userName :String? = null @Expose var gender = 0 var age = 0 @Expose(serialize = true,deserialize = false) var genderDesc = "" } fun User.gsonTest(){ // 序列化 val user = User() user.userName = "Android開發架構" user.age = 18 user.gender = 1 user.genderDesc = "男" val gson = GsonBuilder() .excludeFieldsWithoutExposeAnnotation() .create() val jsonStr = gson.toJson(user) Log.i("cxmydev","json:$jsonStr") // json:{"gender":1,"genderDesc":"男","user_name":"承香墨影"} val newUser = gson.fromJson(jsonStr,User::class.java) Log.i("cxmydev","genderDesc:${newUser.genderDesc}") // genderDesc: }
能夠看到上面的例子中,genderDesc 用於說明性別的描述,使用@Expose(serialize = true,deserialize = false)
標記後,這個字段只參與序列化(serialize = true),而不參與反序列化(deserialize = false)。
須要注意的是,一旦開始使用 @Expose 後,全部的字段都須要顯式的標記是否「暴露」出來。上例中,age
字段沒有 @Expose 註解,因此它在序列化和反序列化的時候,均不會存在。
GSON 中的兩個重要的字段註解,就介紹完了,正確的使用他們能夠解決 80% 關於 GSON 解析數據的問題,更靈活的使用方式,就不是註解能夠解決的了。
就像前面的例子中看到的同樣,想要構造一個 Gson 對象,有兩種方式,new Gson()
和利用 GsonBuilder
構造。
這兩種方式均可以構造一個 Gson 對象,可是在這個 Builder 對象,還提供一些快捷的方法,方便咱們更靈活的解析 JSON。
例如默認狀況下,GSON 是不會解析爲 null 的字段的,而咱們能夠經過.serializeNulls()
方法,來讓 GSON 序列化爲 null 的字段。
// 序列化 val user = User() user.age = 18 user.gender = 1 val jsonStr = GsonBuilder().create().toJson(user) Log.i("cxmydev","json:$jsonStr") // json:{"age":18,"gender":1} val jsonStr1 = GsonBuilder().serializeNulls().create().toJson(user) Log.i("cxmydev","json1:$jsonStr1") // json1:{"age":18,"gender":1,"userName":null}
GsonBuilder 還提供了更多的操做:
.serializeNulls()
:序列化爲 null 的字段。.setDateFormat()
:設置日期格式,例如:setDateFormat("yyyy-MM-dd")
。.disableInnerClassSerialization()
:禁止序列化內部類。.generateNonExcutableJson()
:生成不可直接解析的 JSON,會多 )]}'
這 4 個字符。.disableHtmlEscaping()
:禁止轉移 HTML 標籤.setPrettyPrinting()
:格式化輸出不管是註解仍是 GsonBuilder 中提供的一些方法,都是 GSON 針對一些特殊場景下,爲咱們提供的便捷 API,更復雜一些的場景,就不是它們所能解決的了。
若是前面介紹的規則,都知足不了業務了,不要緊,Gson 還有大招,就是使用 TypeAdapter。
這裏講的 TypeAdapter 是一個泛指,它雖然確實是一個 GSON 庫中的抽象類,但在 GSON 的使用中,它又不是一個類。
使用 TypeAdapter 就須要用到 GsonBuilder 類中的 registerTypeAdapter()
,咱們先來看看這個類的方法實現。
public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) { $Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?> || typeAdapter instanceof InstanceCreator<?> || typeAdapter instanceof TypeAdapter<?>); if (typeAdapter instanceof InstanceCreator<?>) { instanceCreators.put(type, (InstanceCreator) typeAdapter); } if (typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?>) { TypeToken<?> typeToken = TypeToken.get(type); factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter)); } if (typeAdapter instanceof TypeAdapter<?>) { factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter)); } return this; }
能夠看到註冊方法,須要制定一個數據類型,而且它除了支持 TypeAdapter 以外,還支持 JsonSerializer 和 JsonDeserializer。InstanceCreator 的使用場景太少了,就不談了。
TypeAdapter(抽象類)、JsonSerializer(接口)、JsonDeserializer(接口) 均可以理解成咱們前面說的 TypeAdapter 的泛指,他們具體有什麼區別呢?
TypeAdapter 中包含兩個主要的方法 write()
和 read()
方法,分別用於接管序列化和反序列化。而有時候,咱們並不須要處理這兩種狀況,例如咱們只關心 JSON 是如何反序列化成對象的,那就只須要實現 JsonDeserializer 接口的 deserialize()
方法,反之則實現 JsonSerializer 接口的 serialize()
方法,這讓咱們的接管更靈活、更可控。
須要注意的是,TypeAdapter 之因此稱之爲大招,是由於它會致使前面介紹的全部配置都失效。但並非使用了 TypeAdapter 以後,全部的規則都須要咱們本身實現。注意看 registerTypeAdapter()
方法的第一個參數是指定了類型的,它只會針對某個具體的類型進行接管。
舉個例子就清楚了,例如前文中提到,當一個 ""
的 JSON 字段,碰上一個 Int 類型的字段時,就會致使解析失敗,並拋出異常。
// 序列化 val user = User() user.age = 18 user.gender = 1 val jsonStr = "{\"gender\":\"\",\"user_name\":\"Android開發架構\"}" val newUser = GsonBuilder().create().fromJson(jsonStr,User::class.java) Log.i("cxmydev","gender:${gender}")
在上面的例子中,gender
字段應該是一個 Int 值,而 JSON 字符串中的gender
爲 ""
,這樣的代碼,跑起來會拋JsonSyntaxException: java.lang.NumberFormatException: empty String
異常。
咱們實現 JsonDeserializer 接口,來接管反序列化的操做。
class IntegerDefault0Adapter : JsonDeserializer<Int> { override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Int { try { return json!!.getAsInt() } catch (e: NumberFormatException) { return 0 } } }
當轉 Int 出現異常時,返回默認值 0。而後使用 registerTypeAdapter()
方法加入其中。
val newUser = GsonBuilder() .registerTypeAdapter(Int::class.java, IntegerDefault0Adapter()) .create().fromJson(jsonStr,User::class.java) Log.i("cxmydev","gender : ${newUser.gender}") // gender : 0
TypeAdapter 的使用,到這裏就介紹完了,這個大招只要放出來,全部 JSON 解析的問題都再也不是問題。TypeAdapter 的適用場景還不少,能夠根據具體的需求具體實現,這裏就再也不過多介紹了。
另外再補充幾個 TypeAdapter 的細節。
1. registerTypeHierarchyAdapter() 的區別
看看源碼,細心的朋友應該發現了,註冊 TypeAdapter 的時候,還有registerTypeHierarchyAdapter()
方法,它和 registerTypeAdapter()
方法有什麼區別呢?
區別就在於,接管的類型類,是否支持繼承。例如前面例子中,咱們只接管了 Int 類型,而數字類型還有其餘的例如 Long、Float、Double 等並不會命中到。那假如咱們註冊的是這些數字類型的父類 Number 呢?使用 registerTypeAdapter()
也不會被命中,由於類型不匹配。
此時就可使用 registerTypeHierarchyAdapter()
方法來註冊,它是支持繼承的。
2. TypeAdapterFactory 工廠類的使用
使用 registerXxx()
方法能夠鏈式調用,註冊各類 Adapter。
若是嫌麻煩,還可使用 TypeAdapterFacetory 這個 Adapter 工廠,配合registerTypeAdapterFactory()
方法,根據類型來返回不一樣的 Adapter。
其實只是換個了實現方式,並無什麼太大的區別。
3. @JsonAdapter 註解
@JsonAdapter 和前面介紹的 @SerializedName、@Expose 不一樣,不是做用在字段上,而是做用在 Java 類上的。
它指定一個「Adapter」 類,能夠是 TypeAdapter、JsonSerializer 和 JsonDeserializer 這三個中的一個。
@JsonAdapter 註解只是一個更靈活的配置方式而已,瞭解一下便可。
GSON 很好用,可是也是創建在使用正確的基礎上。我見識過一些醜陋的代碼,例如多字段場景下,也在 Java 對象中配套寫上多個字段,再增長一個方法用於返回多個字段中不會 null 的字段。又或者爲了一個 JSON 數據返回的格式,和後端開發「溝通」一下午規範的問題。
堅持規範固然沒有錯,可是由於別人的問題致使本身的工做沒法繼續,就不符合精益思惟了。
不抽象,就沒法深刻思考,咱們仍是就今天的內容作一個簡單的小結。
toJson()
和 fromJson()
兩個簡便的方法序列化和反序列化 JSON 數據。就總結五條吧,多了也記不住。