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)
}
复制代码

如今的结果是非空了,试验成功。

以上。

相关文章
相关标签/搜索