Gson在使用時怎麼對JSON容錯處理

“JSON 是一種具有自描述的、獨立於語言的、輕量級文本數據交換格式,經常被用於數據的存儲和傳輸。而 GSON 可以幫我們快速的將 JSON 數據,在對象之間序列化和反序列化。”

GSON 的 toJson() 和 fromJson() 這兩個方法,是 GSON 最基本的使用方式,它很直觀,也沒什麼好說的。但當被問及 GSON 如何對 JSON 數據容錯,如何靈活序列化和反序列化的時候,就有點抓瞎了。

JSON 數據容錯,最簡單的方式是讓前後端數據保持一致,就根本不存在容錯的問題,但是現實場景中,並不如我們預期的那般美好。

舉兩個簡單的例子:User 類中的姓名,有些接口返回的 Key 值是 name,而有些返回的是 username,如何做容錯呢?再比如 age 字段返回的是如 "18" 這樣的字符串,而 Java 對象將其解析成 Int 類型時,雖然 GSON 有一定的類型容錯性,這樣解析能夠成功,但是如果 age 字段的返回值變成了 "" 呢,如何讓其不拋出異常,並且設置爲默認值 0?

在本文中,我們就來詳細看看,GSON 是如何對數據進行容錯解析的。

GSON 的容錯

GSON 的常規使用

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("lzx","json:$jsonStr")
// json:{"age":18,"gender":1,"userName":"Android開發架構"}

// 反序列化
val newUser = Gson().fromJson(jsonStr,User::class.java)
Log.i("lzx","userName:${newUser.userName}")
// userName:Android開發架構

GSON 很方便,大部分時候並不需要我們額外處理什麼,拿來即用。唯一需要注意的可能就是泛型擦除,針對泛型的解析,無非就是參數的差異而已。

在數據都很規範的情況下,使用 GSON 就只涉及到這兩個方法,但是針對一些特殊的場景,就沒那麼簡單了。

GSON 的註解

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 對象中的用戶名稱,存在不同的接口返回有差異,分別爲:nameuser_nameusername,這種差異也可以用 @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 解析數據的問題,更靈活的使用方式,就不是註解可以解決的了。

GsonBuilder 靈活解析

就像前面的例子中看到的一樣,想要構造一個 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("lzx","json:$jsonStr")
// json:{"age":18,"gender":1}

val jsonStr1 = GsonBuilder().serializeNulls().create().toJson(user)
Log.i("lzx","json1:$jsonStr1")
// json1:{"age":18,"gender":1,"userName":null}

GsonBuilder 還提供了更多的操作:

  1. .serializeNulls() :序列化爲 null 的字段。
  2. .setDateFormat():設置日期格式,例如:setDateFormat("yyyy-MM-dd")。
  3. .disableInnerClassSerialization():禁止序列化內部類。
  4. .generateNonExcutableJson():生成不可直接解析的 JSON,會多 )]}' 這 4 個字符。
  5. .disableHtmlEscaping():禁止轉移 HTML 標籤
  6. .setPrettyPrinting():格式化輸出

無論是註解還是 GsonBuilder 中提供的一些方法,都是 GSON 針對一些特殊場景下,爲我們提供的便捷  API,更復雜一些的場景,就不是它們所能解決的了。

TypeAdapter

如果前面介紹的規則,都滿足不了業務了,沒關係,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 異常。

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 數據返回的格式,和後端開發“溝通”一下午規範的問題。

堅持規範當然沒有錯,但是因爲別人的問題導致自己的工作無法繼續,就不符合精益思維了。

不抽象,就無法深入思考,我們還是就今天的內容做一個簡單的小結。

GSON 可以提供了 toJson() 和 fromJson() 兩個簡便的方法序列化和反序列化 JSON 數據。

通過註解 @SerializedName 可以解決解析字段不一致的問題以及多字段的問題。

通過註解 @Expose 可以解決字段在序列化和反序列化時,字段排除的問題。

GsonBuilder 提供了一些便捷的 API,方便我們解析數據,例如

更靈活的解析,使用 TypeAdapter,可以精準定製序列化和反序列化的全過程。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章