开发者

Moshi 完美解决Gson在kotlin中默认值空的问题详解

开发者 https://www.devze.com 2023-03-16 10:22 出处:网络 作者: ChangJiahong
目录Moshi依赖使用场景存在的问题所有的字段都有默认值的情况部分字段有默认值产生的原因Moshi的特殊json场景1、属性缺失2、属性=null完善Moshi最终效果Moshi
目录
  • Moshi
  • 依赖
  • 使用场景
  • 存在的问题
    • 所有的字段都有默认值的情况
    • 部分字段有默认值
    • 产生的原因
  • Moshi的特殊json场景
    • 1、属性缺失
    • 2、属性=null
  • 完善Moshi
    • 最终效果

      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: JsonReadehttp://www.devze.comr.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[propertyIndejavascriptx] !== 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 prese编程客栈nt, optional, or nullable.
      // 是否调用全属性构造函数标志
      var isFullInitialized = allBindings.size == constructorSize
      for (i in 0 until constructorSize) {
          if (values[i] === ABSENT_VALUE) {
              // 如果等于ABSENT_VALUE,表示该属性没有初始化
              when {
                  // 如果该属性是可缺失的,即该开发者_Python入门属性有默认值,这不需要处理,全属性构造函数标志为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 www.devze.com-> {
                      // 如果是集合就初始化[],否则就是{}对象
                      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,即使我们在类中没有定义a编程客栈ge的默认值

      甚至是对象

      @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默认值空的资料请关注我们其它相关文章!

      0

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      关注公众号