Moshi 完美解决Gson在kotlin中默认值空的问题详解
作者:ChangJiahong
Moshi
Moshi是一个对Kotlin更友好的Json库,square/moshi: A modern JSON library for Kotlin and Java. (github.com)
依赖
implementation("com.squareup.moshi:moshi:1.8.0") kapt("com.squareup.moshi:moshi-kotlin-codegen:1.8.0")
使用场景
基于kotlin-reflection反射需要额外添加 com.squareup.moshi:moshi-kotlin:1.13.0 依赖
// generateAdapter = true 表示使用codegen生成这个类的JsonAdapter @JsonClass(generateAdapter = true) // @Json 标识json中字段名 data class Person(@Json(name = "_name")val name: String, val age: Int) fun main() { val moshi: Moshi = Moshi.Builder() // KotlinJsonAdapterFactory基于kotlin-reflection反射创建自定义类型的JsonAdapter .addLast(KotlinJsonAdapterFactory()) .build() val json = """{"_name": "xxx", "age": 20}""" val person = moshi.adapter(Person::class.java).fromJson(json) println(person) }
- KotlinJsonAdapterFactory用于反射生成数据类的JsonAdapter,如果不使用codegen,那么这个配置是必要的;如果有多个factory,一般将KotlinJsonAdapterFactory添加到最后,因为创建Adapter时是顺序遍历factory进行创建的,应该把反射创建作为最后的手段
@JsonClass(generateAdapter = true)
标识此类,让codegen在编译期生成此类的JsonAdapter,codegen需要数据类和它的properties可见性都是internal/public
- moshi不允许需要序列化的类不是存粹的Java/Kotlin类,比如说Java继承Kotlin或者Kotlin继承Java
存在的问题
所有的字段都有默认值的情况
@JsonClass(generateAdapter = true) data class DefaultAll( val name: String = "me", val age: Int = 17 )
这种情况下,gson 和 moshi都可以正常解析 “{}” json字符
部分字段有默认值
@JsonClass(generateAdapter = true) data class DefaultPart( val name: String = "me", val gender: String = "male", val age: Int ) // 针对以下json gson忽略name,gender属性的默认值,而moshi可以正常解析 val json = """{"age": 17}"""
产生的原因
Gson反序列化对象时优先获取无参构造函数,由于DefaultPart age属性没有默认值,在生成字节码文件后,该类没有无参构造函数,所有Gson最后调用了Unsafe.newInstance函数,该函数不会调用构造函数,执行对象初始化代码,导致name,gender对象是null。
Moshi 通过adpter的方式匹配类的构造函数,使用函数签名最相近的构造函数构造对象,可以是的默认值不丢失,但在官方的例程中,某些情况下依然会出现我们不希望出现的问题。
Moshi的特殊Json场景
1、属性缺失
针对以下类
@JsonClass(generateAdapter = true) data class DefaultPart( val name: String, val gender: String = "male", val age: Int )
若json = """ {"name":"John","age":18}""" Moshi可以正常解析,但如果Json=""" {"name":"John"}"""Moshi会抛出Required value age missing at $ 的异常,
2、属性=null
若Json = """{"name":"John","age":null} ”“”Moshi会抛出Non-null value age was null at $ 的异常
很多时候后台返回的Json数据并不是完全的统一,会存在以上情况,我们可以通过对age属性如gender属性一般设置默认值的方式处理,但可不可以更偷懒一点,可以不用写默认值,系统也能给一个默认值出来。
完善Moshi
分析官方库KotlinJsonAdapterFactory类,发现,以上两个逻辑的判断代码在这里
internal class KotlinJsonAdapter<T>( val constructor: KFunction<T>, // 所有属性的bindingAdpter val allBindings: List<Binding<T, Any?>?>, // 忽略反序列化的属性 val nonIgnoredBindings: List<Binding<T, Any?>>, // 反射类得来的属性列表 val options: JsonReader.Options ) : JsonAdapter<T>() { override fun fromJson(reader: JsonReader): T { val constructorSize = constructor.parameters.size // Read each value into its slot in the array. val values = Array<Any?>(allBindings.size) { ABSENT_VALUE } reader.beginObject() while (reader.hasNext()) { //通过reader获取到Json 属性对应的类属性的索引 val index = reader.selectName(options) if (index == -1) { reader.skipName() reader.skipValue() continue } //拿到该属性的binding val binding = nonIgnoredBindings[index] // 拿到属性值的索引 val propertyIndex = binding.propertyIndex if (values[propertyIndex] !== ABSENT_VALUE) { throw JsonDataException( "Multiple values for '${binding.property.name}' at ${reader.path}" ) } // 递归的方式,初始化属性值 values[propertyIndex] = binding.adapter.fromJson(reader) // 关键的地方1 // 判断 初始化的属性值是否为null ,如果是null ,代表这json字符串中的体现为 age:null if (values[propertyIndex] == null && !binding.property.returnType.isMarkedNullable) { // 抛出Non-null value age was null at $ 异常 throw Util.unexpectedNull( binding.property.name, binding.jsonName, reader ) } } reader.endObject() // 关键的地方2 // 初始化剩下json中没有的属性 // Confirm all parameters are present, optional, or nullable. // 是否调用全属性构造函数标志 var isFullInitialized = allBindings.size == constructorSize for (i in 0 until constructorSize) { if (values[i] === ABSENT_VALUE) { // 如果等于ABSENT_VALUE,表示该属性没有初始化 when { // 如果该属性是可缺失的,即该属性有默认值,这不需要处理,全属性构造函数标志为false constructor.parameters[i].isOptional -> isFullInitialized = false // 如果该属性是可空的,这直接赋值为null constructor.parameters[i].type.isMarkedNullable -> values[i] = null // Replace absent with null. // 剩下的则是属性没有默认值,也不允许为空,如上例,age属性 // 抛出Required value age missing at $ 异常 else -> throw Util.missingProperty( constructor.parameters[i].name, allBindings[i]?.jsonName, reader ) } } } // Call the constructor using a Map so that absent optionals get defaults. val result = if (isFullInitialized) { constructor.call(*values) } else { constructor.callBy(IndexedParameterMap(constructor.parameters, values)) } // Set remaining properties. for (i in constructorSize until allBindings.size) { val binding = allBindings[i]!! val value = values[i] binding.set(result, value) } return result } override fun toJson(writer: JsonWriter, value: T?) { if (value == null) throw NullPointerException("value == null") writer.beginObject() for (binding in allBindings) { if (binding == null) continue // Skip constructor parameters that aren't properties. writer.name(binding.jsonName) binding.adapter.toJson(writer, binding.get(value)) } writer.endObject() }
通过代码的分析,是不是可以在两个关键的逻辑点做以下修改
// 关键的地方1 // 判断 初始化的属性值是否为null ,如果是null ,代表这json字符串中的体现为 age:null if (values[propertyIndex] == null && !binding.property.returnType.isMarkedNullable) { // 抛出Non-null value age was null at $ 异常 //throw Util.unexpectedNull( // binding.property.name, // binding.jsonName, // reader //) // age:null 重置为ABSENT_VALUE值,交由最后初始化剩下json中没有的属性的时候去初始化 values[propertyIndex] = ABSENT_VALUE } // 关键的地方2 // 初始化剩下json中没有的属性 // Confirm all parameters are present, optional, or nullable. // 是否调用全属性构造函数标志 var isFullInitialized = allBindings.size == constructorSize for (i in 0 until constructorSize) { if (values[i] === ABSENT_VALUE) { // 如果等于ABSENT_VALUE,表示该属性没有初始化 when { // 如果该属性是可缺失的,即该属性有默认值,这不需要处理,全属性构造函数标志为false constructor.parameters[i].isOptional -> isFullInitialized = false // 如果该属性是可空的,这直接赋值为null constructor.parameters[i].type.isMarkedNullable -> values[i] = null // Replace absent with null. // 剩下的则是属性没有默认值,也不允许为空,如上例,age属性 // 抛出Required value age missing at $ 异常 else ->{ //throw Util.missingProperty( //constructor.parameters[i].name, //allBindings[i]?.jsonName, //reader //) // 填充默认 val index = options.strings().indexOf(constructor.parameters[i].name) val binding = nonIgnoredBindings[index] val propertyIndex = binding.propertyIndex // 为该属性初始化默认值 values[propertyIndex] = fullDefault(binding) } } } } private fun fullDefault(binding: Binding<T, Any?>): Any? { return when (binding.property.returnType.classifier) { Int::class -> 0 String::class -> "" Boolean::class -> false Byte::class -> 0.toByte() Char::class -> Char.MIN_VALUE Double::class -> 0.0 Float::class -> 0f Long::class -> 0L Short::class -> 0.toShort() // 过滤递归类初始化,这种会导致死循环 constructor.returnType.classifier -> { val message = "Unsolvable as for: ${binding.property.returnType.classifier}(value:${binding.property.returnType.classifier})" throw JsonDataException(message) } is Any -> { // 如果是集合就初始化[],否则就是{}对象 if (Collection::class.java.isAssignableFrom(binding.property.returnType.javaType.rawType)) { binding.adapter.fromJson("[]") } else { binding.adapter.fromJson("{}") } } else -> {} } }
最终效果
"""{"name":"John","age":null} ”“” age会被初始化成0,
"""{"name":"John"} ”“” age依然会是0,即使我们在类中没有定义age的默认值
甚至是对象
@JsonClass(generateAdapter = true) data class DefaultPart( val name: String, val gender: String = "male", val age: Int, val action:Action ) class Action(val ac:String)
最终Action也会产生一个Action(ac:"")的值
data class RestResponse<T>( val code: Int, val msg: String="", val data: T? ) { fun isSuccess() = code == 1 fun checkData() = data != null fun successRestData() = isSuccess() && checkData() fun requsetData() = data!! } class TestD(val a:Int,val b:String,val c:Boolean,val d:List<Test> ) { } class Test(val a:Int,val b:String,val c:Boolean=true) val s = """ { "code":200, "msg":"ok", "data":[{"a":0,"c":false,"d":[{"b":null}]}]} """.trimIndent() val a :RestResponse<List<TestD>>? = s.fromJson()
最终a为
{"code":200,"msg":"ok","data":[{"a":0,"b":"","c":false,"d":[{"a":0,"b":"","c":true}]}]}
以上就是Moshi 完美解决Gson在kotlin中默认值空的问题详解的详细内容,更多关于Moshi解决Gson在kotlin默认值空的资料请关注脚本之家其它相关文章!