Gson 支持 Kotlin 空安全的一種嘗試

1. 緣起

衆所周知 Kotlin 是一種支持空安全的語言,它在類型聲明的時候就能夠指定該值是 NonNull 仍是 Nullable 的。那麼,當咱們聲明一個這種類型的 data class,而後用Gson將其反序列化,獲得的對象也會是空安全的麼?java

data class Test(
    val test: String
)

fun main() {
    val test = Gson().fromJson<Test>("{\"test\":null}", Test::class.java)
    println(test.test == null)

}
複製代碼

執行這段代碼以後咱們能夠發現,明明是指定非空的 test ,卻等於 null 了。json

2. 覓蹤

辣麼,這是爲何呢?經過翻閱 Gson 的源碼,能夠看到在 Gson 內部是調用 ConstructorConstructor 來建立實例的,而 ConstructorConstructor 內部的建立方式是 1.先調用默認的空參數構造方法;2.若是沒有就本身造一個空參數構造方法,來建立實例。安全

這樣結果就很明顯了,上例中的 data class 顯然沒有空參數構造方法,因此 Gson 本身造了一個來生成 test 對象,也就所以繞過了 kotlin 的空檢查。spa

那加一個構造方法會不會有效呢?code

data class Test(
    val test: String = ""
)
複製代碼

咱們給 test 加了默認參數,這樣就有了空參數的構造方法供 Gson 調用。運行以後就會發現…… WTF 爲何仍是空的?!對象

因而繼續 RTFSC,發現是在這裏賦值的:rem

Object fieldValue = typeAdapter.read(reader);
if (fieldValue != null || !isPrimitive) {
    field.set(value, fieldValue);
}
複製代碼

因而真相大白了,直接反射賦值固然和 Kotlin 的空安全麼得關係了啊。get

3. 纏綿

辣麼,怎麼讓他們能夠發生關係呢?首先,Gson 是不支持也不打算支持 Kotlin 的;其次,Kotlin 中的各類限制也限制不到 Gson 目前的解析方法這裏。因此只能曲線救國了,若是 data class 中的可空值的默認參數是 null,而非空值的默認參數都不是 null,這樣咱們就有了 1.默認的空參數構造方法、2.默認值這兩樣東西來進行下一步處理。源碼

在執行賦值的時候,咱們能夠獲得當前即將被賦值變量的值,若是這個值是非空的話,就能夠認爲是一個非空的變量,若是新值是空,這時咱們保留原值而不將空值寫入,就實現了 Gson 對 Kotlin 空安全的支持。即改爲以下所示:string

Object fieldValue = typeAdapter.read(reader);
if (fieldValue != null) {
    field.set(value, fieldValue);
} else if (!isPrimitive) {
    if (field.get(value) == null) {
        field.set(value, null);
    }
}
複製代碼

這時咱們擁有了一個新的 AdapterFactory,怎麼替換舊的呢?俗話說得好,反射解決一些問題:

val GSON = Gson().also {
    var field = it.javaClass.getDeclaredField("factories")
    field.isAccessible = true
    val factoryList = ArrayList(field.get(it) as List<TypeAdapterFactory>)
    field = it.javaClass.getDeclaredField("constructorConstructor")
    field.isAccessible = true
    val constructorConstructor = field.get(it) as ConstructorConstructor

    field = it.javaClass.getDeclaredField("jsonAdapterFactory")
    field.isAccessible = true
    val jsonAdapterFactory = field.get(it) as JsonAdapterAnnotationTypeAdapterFactory
    factoryList.removeAt(factoryList.size - 1)
    factoryList.add(
        NullSafeReflectiveTypedAdapterFactory(
            constructorConstructor,
            FieldNamingPolicy.IDENTITY,
            Excluder.DEFAULT,
            jsonAdapterFactory
        )
    )
    field = it.javaClass.getDeclaredField("factories")
    field.isAccessible = true

    field.set(it, Collections.unmodifiableList(factoryList))
}
複製代碼

再來跑一下新的樣例:

data class Test(
    val test: String = ""
)

fun main() {
    val test = GSON.fromJson<Test>("{\"test\":null}", Test::class.java)
    println(test.test == null)
}
複製代碼

如今的結果是非空了,試驗成功。

以上。

相關文章
相關標籤/搜索