Moshi?完美解決Gson在kotlin中默認(rèn)值空的問(wèn)題詳解
Moshi
Moshi是一個(gè)對(duì)Kotlin更友好的Json庫(kù),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")
使用場(chǎng)景
基于kotlin-reflection反射需要額外添加 com.squareup.moshi:moshi-kotlin:1.13.0 依賴
// generateAdapter = true 表示使用codegen生成這個(gè)類的JsonAdapter
@JsonClass(generateAdapter = true)
// @Json 標(biāo)識(shí)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,那么這個(gè)配置是必要的;如果有多個(gè)factory,一般將KotlinJsonAdapterFactory添加到最后,因?yàn)閯?chuàng)建Adapter時(shí)是順序遍歷factory進(jìn)行創(chuàng)建的,應(yīng)該把反射創(chuàng)建作為最后的手段
@JsonClass(generateAdapter = true)標(biāo)識(shí)此類,讓codegen在編譯期生成此類的JsonAdapter,codegen需要數(shù)據(jù)類和它的properties可見(jiàn)性都是internal/public
- moshi不允許需要序列化的類不是存粹的Java/Kotlin類,比如說(shuō)Java繼承Kotlin或者Kotlin繼承Java
存在的問(wèn)題
所有的字段都有默認(rèn)值的情況
@JsonClass(generateAdapter = true)
data class DefaultAll(
val name: String = "me",
val age: Int = 17
)
這種情況下,gson 和 moshi都可以正常解析 “{}” json字符
部分字段有默認(rèn)值
@JsonClass(generateAdapter = true)
data class DefaultPart(
val name: String = "me",
val gender: String = "male",
val age: Int
)
?
// 針對(duì)以下json gson忽略name,gender屬性的默認(rèn)值,而moshi可以正常解析
val json = """{"age": 17}"""
?
產(chǎn)生的原因
Gson反序列化對(duì)象時(shí)優(yōu)先獲取無(wú)參構(gòu)造函數(shù),由于DefaultPart age屬性沒(méi)有默認(rèn)值,在生成字節(jié)碼文件后,該類沒(méi)有無(wú)參構(gòu)造函數(shù),所有Gson最后調(diào)用了Unsafe.newInstance函數(shù),該函數(shù)不會(huì)調(diào)用構(gòu)造函數(shù),執(zhí)行對(duì)象初始化代碼,導(dǎo)致name,gender對(duì)象是null。
Moshi 通過(guò)adpter的方式匹配類的構(gòu)造函數(shù),使用函數(shù)簽名最相近的構(gòu)造函數(shù)構(gòu)造對(duì)象,可以是的默認(rèn)值不丟失,但在官方的例程中,某些情況下依然會(huì)出現(xiàn)我們不希望出現(xiàn)的問(wèn)題。
Moshi的特殊Json場(chǎng)景
1、屬性缺失
針對(duì)以下類
@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會(huì)拋出Required value age missing at $ 的異常,
2、屬性=null
若Json = """{"name":"John","age":null} ”“”Moshi會(huì)拋出Non-null value age was null at $ 的異常
很多時(shí)候后臺(tái)返回的Json數(shù)據(jù)并不是完全的統(tǒng)一,會(huì)存在以上情況,我們可以通過(guò)對(duì)age屬性如gender屬性一般設(shè)置默認(rèn)值的方式處理,但可不可以更偷懶一點(diǎn),可以不用寫(xiě)默認(rèn)值,系統(tǒng)也能給一個(gè)默認(rèn)值出來(lái)。
完善Moshi
分析官方庫(kù)KotlinJsonAdapterFactory類,發(fā)現(xiàn),以上兩個(gè)邏輯的判斷代碼在這里
internal class KotlinJsonAdapter<T>(
val constructor: KFunction<T>,
// 所有屬性的bindingAdpter
val allBindings: List<Binding<T, Any?>?>,
// 忽略反序列化的屬性
val nonIgnoredBindings: List<Binding<T, Any?>>,
// 反射類得來(lái)的屬性列表
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()) {
//通過(guò)reader獲取到Json 屬性對(duì)應(yīng)的類屬性的索引
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中沒(méi)有的屬性
// Confirm all parameters are present, optional, or nullable.
// 是否調(diào)用全屬性構(gòu)造函數(shù)標(biāo)志
var isFullInitialized = allBindings.size == constructorSize
for (i in 0 until constructorSize) {
if (values[i] === ABSENT_VALUE) {
// 如果等于ABSENT_VALUE,表示該屬性沒(méi)有初始化
when {
// 如果該屬性是可缺失的,即該屬性有默認(rèn)值,這不需要處理,全屬性構(gòu)造函數(shù)標(biāo)志為false
constructor.parameters[i].isOptional -> isFullInitialized = false
// 如果該屬性是可空的,這直接賦值為null
constructor.parameters[i].type.isMarkedNullable -> values[i] = null // Replace absent with null.
// 剩下的則是屬性沒(méi)有默認(rèn)值,也不允許為空,如上例,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ò)代碼的分析,是不是可以在兩個(gè)關(guān)鍵的邏輯點(diǎ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中沒(méi)有的屬性的時(shí)候去初始化
values[propertyIndex] = ABSENT_VALUE
}
?
// 關(guān)鍵的地方2
// 初始化剩下json中沒(méi)有的屬性
// Confirm all parameters are present, optional, or nullable.
// 是否調(diào)用全屬性構(gòu)造函數(shù)標(biāo)志
var isFullInitialized = allBindings.size == constructorSize
for (i in 0 until constructorSize) {
if (values[i] === ABSENT_VALUE) {
// 如果等于ABSENT_VALUE,表示該屬性沒(méi)有初始化
when {
// 如果該屬性是可缺失的,即該屬性有默認(rèn)值,這不需要處理,全屬性構(gòu)造函數(shù)標(biāo)志為false
constructor.parameters[i].isOptional -> isFullInitialized = false
// 如果該屬性是可空的,這直接賦值為null
constructor.parameters[i].type.isMarkedNullable -> values[i] = null // Replace absent with null.
// 剩下的則是屬性沒(méi)有默認(rèn)值,也不允許為空,如上例,age屬性
// 拋出Required value age missing at $ 異常
else ->{
//throw Util.missingProperty(
//constructor.parameters[i].name,
//allBindings[i]?.jsonName,
//reader
//)
// 填充默認(rèn)
val index = options.strings().indexOf(constructor.parameters[i].name)
val binding = nonIgnoredBindings[index]
val propertyIndex = binding.propertyIndex
// 為該屬性初始化默認(rèn)值
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()
// 過(guò)濾遞歸類初始化,這種會(huì)導(dǎo)致死循環(huán)
constructor.returnType.classifier -> {
val message =
"Unsolvable as for: ${binding.property.returnType.classifier}(value:${binding.property.returnType.classifier})"
throw JsonDataException(message)
}
is Any -> {
// 如果是集合就初始化[],否則就是{}對(duì)象
if (Collection::class.java.isAssignableFrom(binding.property.returnType.javaType.rawType)) {
binding.adapter.fromJson("[]")
} else {
binding.adapter.fromJson("{}")
}
}
else -> {}
}
}
最終效果
"""{"name":"John","age":null} ”“” age會(huì)被初始化成0,
"""{"name":"John"} ”“” age依然會(huì)是0,即使我們?cè)陬愔袥](méi)有定義age的默認(rèn)值
甚至是對(duì)象
@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也會(huì)產(chǎn)生一個(gè)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中默認(rèn)值空的問(wèn)題詳解的詳細(xì)內(nèi)容,更多關(guān)于Moshi解決Gson在kotlin默認(rèn)值空的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android檢測(cè)手機(jī)多點(diǎn)觸摸點(diǎn)數(shù)的方法
這篇文章主要為大家詳細(xì)介紹了Android檢測(cè)手機(jī)多點(diǎn)觸摸點(diǎn)數(shù)的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Android主項(xiàng)目與Module中R類的區(qū)別詳解
這篇文章主要給大家介紹了關(guān)于Android主項(xiàng)目與Module中R類的區(qū)別的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02
Golang+Android基于HttpURLConnection實(shí)現(xiàn)的文件上傳功能示例
這篇文章主要介紹了Golang+Android基于HttpURLConnection實(shí)現(xiàn)的文件上傳功能,結(jié)合具體實(shí)例形式分析了Android基于HttpURLConnection的客戶端結(jié)合Go語(yǔ)言服務(wù)器端實(shí)現(xiàn)文件上傳功能的操作技巧,需要的朋友可以參考下2017-03-03
Android 兩個(gè)Service的相互監(jiān)視實(shí)現(xiàn)代碼
這篇文章主要介紹了Android 兩個(gè)Service的相互監(jiān)視實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10
Android根據(jù)包名停止其他應(yīng)用程序的方法
這篇文章主要介紹了Android根據(jù)包名停止其他應(yīng)用程序,需要的朋友可以參考下2020-03-03
Android基于google Zxing實(shí)現(xiàn)各類二維碼掃描效果
這篇文章主要介紹了Android基于google Zxing實(shí)現(xiàn)各類二維碼掃描效果的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-02-02
Android ListView中動(dòng)態(tài)添加RaidoButton的實(shí)例詳解
這篇文章主要介紹了Android ListView中動(dòng)態(tài)添加RaidoButton的實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-08-08

