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.05fconst 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不能解决字段不存在的问题
