Moshi?完美解決Gson在kotlin中默認值空的問題詳解
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反射創(chuàng)建自定義類型的JsonAdapter .addLast(KotlinJsonAdapterFactory()) .build() val json = """{"_name": "xxx", "age": 20}""" val person = moshi.adapter(Person::class.java).fromJson(json) println(person) }
- KotlinJsonAdapterFactory用于反射生成數(shù)據(jù)類的JsonAdapter,如果不使用codegen,那么這個配置是必要的;如果有多個factory,一般將KotlinJsonAdapterFactory添加到最后,因為創(chuàng)建Adapter時是順序遍歷factory進行創(chuàng)建的,應該把反射創(chuàng)建作為最后的手段
@JsonClass(generateAdapter = true)
標識此類,讓codegen在編譯期生成此類的JsonAdapter,codegen需要數(shù)據(jù)類和它的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}""" ?
產(chǎn)生的原因
Gson反序列化對象時優(yōu)先獲取無參構(gòu)造函數(shù),由于DefaultPart age屬性沒有默認值,在生成字節(jié)碼文件后,該類沒有無參構(gòu)造函數(shù),所有Gson最后調(diào)用了Unsafe.newInstance函數(shù),該函數(shù)不會調(diào)用構(gòu)造函數(shù),執(zhí)行對象初始化代碼,導致name,gender對象是null。
Moshi 通過adpter的方式匹配類的構(gòu)造函數(shù),使用函數(shù)簽名最相近的構(gòu)造函數(shù)構(gòu)造對象,可以是的默認值不丟失,但在官方的例程中,某些情況下依然會出現(xiàn)我們不希望出現(xiàn)的問題。
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數(shù)據(jù)并不是完全的統(tǒng)一,會存在以上情況,我們可以通過對age屬性如gender屬性一般設(shè)置默認值的方式處理,但可不可以更偷懶一點,可以不用寫默認值,系統(tǒng)也能給一個默認值出來。
完善Moshi
分析官方庫KotlinJsonAdapterFactory類,發(fā)現(xiàn),以上兩個邏輯的判斷代碼在這里
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) ? // 關(guān)鍵的地方1 // 判斷 初始化的屬性值是否為null ,如果是null ,代表這json字符串中的體現(xiàn)為 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() ? // 關(guān)鍵的地方2 // 初始化剩下json中沒有的屬性 // Confirm all parameters are present, optional, or nullable. // 是否調(diào)用全屬性構(gòu)造函數(shù)標志 var isFullInitialized = allBindings.size == constructorSize for (i in 0 until constructorSize) { if (values[i] === ABSENT_VALUE) { // 如果等于ABSENT_VALUE,表示該屬性沒有初始化 when { // 如果該屬性是可缺失的,即該屬性有默認值,這不需要處理,全屬性構(gòu)造函數(shù)標志為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() } ?
通過代碼的分析,是不是可以在兩個關(guān)鍵的邏輯點做以下修改
?// 關(guān)鍵的地方1 // 判斷 初始化的屬性值是否為null ,如果是null ,代表這json字符串中的體現(xiàn)為 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 } ? // 關(guān)鍵的地方2 // 初始化剩下json中沒有的屬性 // Confirm all parameters are present, optional, or nullable. // 是否調(diào)用全屬性構(gòu)造函數(shù)標志 var isFullInitialized = allBindings.size == constructorSize for (i in 0 until constructorSize) { if (values[i] === ABSENT_VALUE) { // 如果等于ABSENT_VALUE,表示該屬性沒有初始化 when { // 如果該屬性是可缺失的,即該屬性有默認值,這不需要處理,全屬性構(gòu)造函數(shù)標志為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() // 過濾遞歸類初始化,這種會導致死循環(huán) 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也會產(chǎn)生一個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中默認值空的問題詳解的詳細內(nèi)容,更多關(guān)于Moshi解決Gson在kotlin默認值空的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang+Android基于HttpURLConnection實現(xiàn)的文件上傳功能示例
這篇文章主要介紹了Golang+Android基于HttpURLConnection實現(xiàn)的文件上傳功能,結(jié)合具體實例形式分析了Android基于HttpURLConnection的客戶端結(jié)合Go語言服務(wù)器端實現(xiàn)文件上傳功能的操作技巧,需要的朋友可以參考下2017-03-03Android 兩個Service的相互監(jiān)視實現(xiàn)代碼
這篇文章主要介紹了Android 兩個Service的相互監(jiān)視實現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10Android基于google Zxing實現(xiàn)各類二維碼掃描效果
這篇文章主要介紹了Android基于google Zxing實現(xiàn)各類二維碼掃描效果的相關(guān)資料,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-02-02Android ListView中動態(tài)添加RaidoButton的實例詳解
這篇文章主要介紹了Android ListView中動態(tài)添加RaidoButton的實例詳解的相關(guān)資料,需要的朋友可以參考下2017-08-08