1. 缘起
众所周知 Kotlin 是一种支持空安全的语言,它在类型声明的时候就可以指定该值是 NonNull 还是 Nullable 的。那么,当我们声明一个这种类型的 data class
,然后用Gson将其反序列化,得到的对象也会是空安全的么?
data class Test(
val test: String
)
fun main() {
val test = Gson().fromJson<Test>("{\"test\":null}", Test::class.java)
println(test.test == null)
}
执行这段代码之后我们可以发现,明明是指定非空的 test ,却等于 null 了。
2. 觅踪
辣么,这是为什么呢?通过翻阅 Gson 的源码,可以看到在 Gson 内部是调用 ConstructorConstructor
来创建实例的,而 ConstructorConstructor
内部的创建方式是 1.先调用默认的空参数构造方法;2.如果没有就自己造一个空参数构造方法,来创建实例。
这样结果就很明显了,上例中的 data class
显然没有空参数构造方法,所以 Gson 自己造了一个来生成 test 对象,也就因此绕过了 kotlin 的空检查。
那加一个构造方法会不会有效呢?
data class Test(
val test: String = ""
)
我们给 test 加了默认参数,这样就有了空参数的构造方法供 Gson 调用。运行之后就会发现…… WTF 为什么还是空的?!
于是继续 RTFSC,发现是在这里赋值的:
Object fieldValue = typeAdapter.read(reader);
if (fieldValue != null || !isPrimitive) {
field.set(value, fieldValue);
}
于是真相大白了,直接反射赋值当然和 Kotlin 的空安全么得关系了啊。
3. 缠绵
辣么,怎么让他们可以发生关系呢?首先,Gson 是不支持也不打算支持 Kotlin 的;其次,Kotlin 中的各种限制也限制不到 Gson 目前的解析方法这里。所以只能曲线救国了,如果 data class
中的可空值的默认参数都是 null,而非空值的默认参数都不是 null,这样我们就有了 1.默认的空参数构造方法、2.默认值这两样东西来进行下一步处理。
在执行赋值的时候,我们可以得到当前即将被赋值变量的值,如果这个值是非空的话,就可以认为是一个非空的变量,如果新值是空,这时我们保留原值而不将空值写入,就实现了 Gson 对 Kotlin 空安全的支持。即改成如下所示:
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)
}
现在的结果是非空了,试验成功。
以上。