1. 背景
-
Gson
作为json
解析最有名的库,咱们也在多处运用或借鉴其完成。可是json
解析本就存在许多问题,而且这些问题轻则导致数据丢失,重则直接崩溃,咱们应该对他引起注重。在项目更新kotlin之后,更由于gson库是基于java设计的,进而引出了咱们今日遇到的问题。
2. 问题
- 当通过
kotlin
调用Gson.fromJson(“json”, Class<T>)
解析json
,而且目标通过kotlin
创建时,有可能在非空的字段解析出null
,例如运用下列json
和class
进行解析。
data class LowGsonData(
@SerializedName("name") var name: String,
@SerializedName("age") var age: Int,
@SerializedName("address") var address: String
)
data class LowGsonData(
@SerializedName("name") var name: String = "",
@SerializedName("age") var age?: Int = 0,
@SerializedName("address") var address: String = ""
)
class TestGson {
@Test
fun test() {
val json = "{\"name\":\"cong\",\"age\":11}"
// val json2 = "{\"name\":,\"age\":11}"
val testData = Gson().fromJson(json, LowGsonData::class.java)
println("testData: name = ${testData.name} age = ${testData.age} address = ${testData.address}")
}
}
- 在咱们运用上述两个LowGsonData对json进行解析时,咱们关注一下
testData.address
会被解析为什么?
address
不是非空的吗?为什么这里address是空?这样在事务代码很简单由于kotlin的空安全检测,导致空指针问题!
3. 寻觅原因
1.把kotlin data转为java
- 由于
kotlin
终究都是转化成java
字节码运行在虚拟机上的,所以咱们先把这个类转为java
代码便利咱们看清这个目标的实质
public final class TestGsonData {
@SerializedName("name")
@NotNull
private String name;
@SerializedName("age")
private int age;
@SerializedName("address")
@NotNull
private String address;
public TestGsonData(@NotNull String name, int age, @NotNull String address) {
Intrinsics.checkNotNullParameter(name, "name");
Intrinsics.checkNotNullParameter(address, "address");
super();
this.name = name;
this.age = age;
this.address = address;
}
}
- 看着好像没啥问题,调用这个结构函数仍然能确保数据非空。那咱们就需求继续剖析gson是怎样结构出目标的?
2.剖析Gson是怎么结构目标的
-
Gson
的逻辑,一般都是根据读取到的类型,然后找对应的TypeAdapter
处理,本例为一般自定义目标,所以会终究走到ReflectiveTypeAdapterFactory.create
返回相应的TypeAdapter
。其中包含结构目标的办法 3 个:
(1)newDefaultConstructor
:咱们大部分目标都是通过这个当地创建的,获取无参的结构函数,如果可以找到,则通过 newInstance
反射的办法构建目标。
private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {
try {
final Constructor<? super T> constructor = rawType.getDeclaredConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return new ObjectConstructor<T>() {
@SuppressWarnings("unchecked") // T is the same raw type as is requested
@Override public T construct() {
Object[] args = null;
return (T) constructor.newInstance(args);
// 省略了一些反常处理
};
} catch (NoSuchMethodException e) {
return null;
}
}
(2)newDefaultImplementationConstructor
:都是一些集合类相关目标的逻辑。
(3)newUnsafeAllocator
:通过 sun.misc.Unsafe
结构了一个目标,是用来拜访 hidden API
,以及获取必定的操作内存的才能。
public static UnsafeAllocator create() {
// try JVM
// public class Unsafe {
// public Object allocateInstance(Class<?> type);
// }
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field f = unsafeClass.getDeclaredField("theUnsafe");
f.setAccessible(true);
final Object unsafe = f.get(null);
final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
return new UnsafeAllocator() {
@Override
@SuppressWarnings("unchecked")
public <T> T newInstance(Class<T> c) throws Exception {
assertInstantiable(c);
return (T) allocateInstance.invoke(unsafe, c);
}
};
} catch (Exception ignored) {
}
// try dalvikvm, post-gingerbread use ObjectStreamClass
// try dalvikvm, pre-gingerbread , ObjectInputStream
}
-
现在咱们现已知道了,当这个目标没有无参结构函数时,第一个办法不成立,终究会通过
unSafe
办法构建目标。尽管gson
本身的设计,通过三种办法来确保目标创建成功很棒,可是这恰好在Unsafe结构中绕过了kotlin
的空安全查看。 -
所以
Unsafe
为啥没能契合空安全呢?由于
UnSafe
是直接获取内存中的值,String
目标在没有赋值时正好是null
,而且json
里没有对应值,最后将不会覆盖他。 -
好的水落石出了,那有什么改善办法吗?有,尽量满意第一个条件。
-
kotlin
的data calss
只要有一个特点没有给初始值就不会生成无参结构办法。所以要想确保gson
解析场景的非空性,咱们应该给所有非可空特点附初始值。或者一开始就设置可空,并在事务代码中判空。
data class FullGsonData(
@SerializedName("name") var name: String = "",
@SerializedName("age") var age: Int = 0,
@SerializedName("address") var address: String = ""
)
-
可是全都这么写吗?毕竟有些目标在事务中需求结构办法传入一些必传的值。那我就比较贪心,我既要又要还要。
-
我的主意: 在聊天
elem
的场景,结合事务,封装一个工厂供事务结构目标。并在data class
中继续保持非空结构。 -
有没有其他好的主意?
- 通过
kotlin
插件规避
- 通过
4. 怎么规避该问题:
通过调研我以为比较好的办法有:
1.引进noarg和allopen主动生成无参结构函数。
- www.kotlincn.net/docs/refere…
- /post/689303…
2.测验对现有项目中运用的json解析库进行升级改造
如moshi,同时适配特点缺失、特点反常等在生产中可能会遇到的问题。
- /post/684490…
- /post/720957…
5. 参考:
- blog.csdn.net/lmj62356579…
- blog.csdn.net/java_cpp_/a…
- www.kotlincn.net/docs/refere…