欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Moshi?完美解決Gson在kotlin中默認值空的問題詳解

 更新時間:2023年03月16日 08:47:24   作者:ChangJiahong  
這篇文章主要為大家介紹了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)文章

最新評論