中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Moshi如何解決Gson在kotlin中默認值空問題

發布時間:2023-03-16 09:44:33 來源:億速云 閱讀:289 作者:iii 欄目:開發技術

本文小編為大家詳細介紹“Moshi如何解決Gson在kotlin中默認值空問題”,內容詳細,步驟清晰,細節處理妥當,希望這篇“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反射創建自定義類型的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中默認值空問題”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    桦南县| 资源县| 介休市| 刚察县| 汶川县| 宜春市| 尉犁县| 天柱县| 东安县| 庄河市| 凤翔县| 建昌县| 滦平县| 绵阳市| 延庆县| 贵州省| 庄河市| 佳木斯市| 镇巴县| 宁晋县| 项城市| 广汉市| 满城县| 阳泉市| 新民市| 达拉特旗| 通江县| 醴陵市| 铁力市| 龙泉市| 南和县| 武定县| 茂名市| 方山县| 大渡口区| 利津县| 景宁| 保康县| 城固县| 寿宁县| 堆龙德庆县|