data是kotlin的数据类,Gson是最常用的json序列化框架,现在探讨二者的兼容性问题
Gson版本更新到最新
越高的版本兼容kotlin数据类型的概率越高,所以升级到最新版本,当前分析时Gson的版本为implementation 'com.google.code.gson:gson:2.8.7'
分析Gson的反序列化data
实体bean
data class Partner(
var name: String,
val attack: Int, // 攻击力
val defence: Int, // 防御力
val criticalRate: Float = 0.05f, // 暴击率(0-100%),默认5%
val criticalDMG: Float = 0.5f, // 暴击伤害 (0-∞),默认50%
var comeOnTheStage: Boolean, // 当前是否出场
var prologue: String? // 开场白
)
构造数据
fun m1(){
val amber = Partner("Amber", 206, 189, 0.32f, 1.88f, true, "侦查骑士,登场!")
val amberJson = gson.toJson(amber)
println(amberJson)
}
上面获取到的数据提出成String
const val json1 = """
{"name":"Amber","attack":206,"defence":189,"criticalRate":0.32,"criticalDMG":0.88,"comeOnTheStage":true,"prologue":"侦查骑士,登场!"}
"""
反序列化完整数据
fun m2(){
val bean1 = gson.fromJson(json1, Partner::class.java)
println(bean1)
}
Partner(name=Amber, attack=206, defence=189, criticalRate=0.32, criticalDMG=0.88, comeOnTheStage=true, prologue=侦查骑士,登场!)
结论:
- 一个完整数据的反序列化是没有问题的
反序列化不完整数据
构造一个不完整的数据
const val json2 = """
{"attack":206,"defence":189,"criticalDMG":0.88,"comeOnTheStage":true,"prologue":"侦查骑士,登场!"}
"""
去掉了一个不可为null的name
字段,去掉一个没有默认值的defence
字段,去掉一个含有默认值的criticalRate
字段
反序列化之后:
fun m2(){
val bean1 = gson.fromJson(json2, Partner::class.java)
println(bean1)
}
Partner(name=null, attack=206, defence=0, criticalRate=0.0, criticalDMG=0.88, comeOnTheStage=true, prologue=侦查骑士,登场!)
表现:
- name字段被映射成null,在kotlin中出现了null-safe的问题
- defece字段被映射成0,对应java的基本数据类型被赋值为默认值
- criticalRate字段被映射成0.0f,是java中float类型的默认值,而当前data中的默认值会丢失
分析Gson反序列化
Gson的反序列化过程:
- 查找固定类型的解析器
- 一般通过这个解决String、int这种通用类型赋值的问题,比如将(null 改为 “”)
- 走空参数的构造函数
- data数据没有空参数的构造函数
- Unsafe类构造bean,不需要走构造函数
- data数据是通过这种方式创建的,所以构造函数的默认赋值不生效
解决反序列化的空安全问题
data中所有字段都标记为Nullable,即加上?
data class Partner2(
var name: String?,
val attack: Int?, // 攻击力
val defence: Int?, // 防御力
val criticalRate: Float? = 0.05f, // 暴击率(0-100%),默认5%
val criticalDMG: Float? = 0.5f, // 暴击伤害 (0-∞),默认50%
var comeOnTheStage: Boolean?, // 当前是否出场
var prologue: String? // 开场白
)
但是这样使用起来不方便,而且很多必须不为null的字段,比如某个boolean值,为null无法判断。
结合上面java的8种基本数据类型反序列化会赋默认值,那么data中所有的非基本数据类型必须加上?来接收
data class Partner3(
var name: String?,
val attack: Int, // 攻击力
val defence: Int, // 防御力
val criticalRate: Float = 0.05f, // 暴击率(0-100%),默认5%
val criticalDMG: Float = 0.5f, // 暴击伤害 (0-∞),默认50%
var comeOnTheStage: Boolean, // 当前是否出场
var prologue: String? // 开场白
)
这样就不会出现空安全问题,而且用起来也比较方便
结合gson的解析构造器,可以给String等类型赋默认值,必然不为null的类型更多
解决data类默认值丢失的问题
Unsafe构造的data必然会丢失data上的默认值,必须走构造函数才能赋值,结合Gson反序列化,会调用无参构造函数,那么就提供一个无参的构造函数
data class Partner4(
var name: String,
val attack: Int, // 攻击力
val defence: Int, // 防御力
val criticalRate: Float = DEFAULT_CORTICAL_RATE, // 暴击率(0-100%),默认5%
val criticalDMG: Float = DEFAULT_CORTICAL_DMG, // 暴击伤害 (0-∞),默认50%
var comeOnTheStage: Boolean = false, // 当前是否出场
var prologue: String? // 开场白
) {
companion object {
const val DEFAULT_CORTICAL_RATE = 0.05f
const val DEFAULT_CORTICAL_DMG = 0.5f
}
constructor() : this("", 0, 0, DEFAULT_CORTICAL_RATE, DEFAULT_CORTICAL_DMG, false, null)
}
这样写起来比较麻烦,但是是通过构造函数去生成类,而不是走Unsafe,稳定性更高,而且带有data默认值
代码如下
const val json3 = """
{"attack":206,"criticalDMG":0.88,"prologue":"侦查骑士,登场!"}
"""
fun m2(){
val bean1 = gson.fromJson(json3, Partner4::class.java)
println(bean1)
}
Partner4(name=, attack=206, defence=0, criticalRate=0.05, criticalDMG=0.88, comeOnTheStage=false, prologue=侦查骑士,登场!)
最终解决方案
- kotlin data类中的非java基本类型(包含能保证no-null的类型),必须加上?类标记nullable
- 提供一个空参数构造函数,手动赋默认值,然后由反序列化的解析数据覆盖
具体使用哪一种,可以按照接口的复杂度自由选择
补充:
如果某个字段在反序列化中不存在,那么反序列化依然会有nullsafe问题
const val json3 = """
{"name":null,"attack":206,"criticalDMG":0.88,"prologue":"侦查骑士,登场!"}
"""
const val json4 = """
{"attack":206,"criticalDMG":0.88,"prologue":"侦查骑士,登场!"}
"""
如上面代码,json4比json3少一个name字段
使用TypeAdapter创建Gson
class StringAdapter : TypeAdapter<String>() {
override fun write(out: JsonWriter?, value: String?) {
out?.let { jsonWriter ->
value?.let {
jsonWriter.value(it)
} ?: jsonWriter.nullValue() // 此方法不会写该字段
}
}
override fun read(`in`: JsonReader?): String {
`in`?.let {
if (it.peek() == JsonToken.NULL) { // 如果返回nulL,转为""
it.nextNull()
return ""
}
return it.nextString()
}
return ""
}
}
解析json3结果为
Partner(name=, attack=206, defence=0, criticalRate=0.0, criticalDMG=0.88, comeOnTheStage=false, prologue=侦查骑士,登场!)
解析json4结果为
Partner(name=null, attack=206, defence=0, criticalRate=0.0, criticalDMG=0.88, comeOnTheStage=false, prologue=侦查骑士,登场!)
结论:
- 使用TypeAdapter可以解决字段值为null的默认赋值问题
- 使用TypeAdapter不能解决字段不存在的问题