首页 > 软件编程 > Android > Moshi解决Gson kotlin默认值空

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

作者: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反射创建自定义类型的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()

{"code":200,"msg":"ok","data":[{"a":0,"b":"","c":false,"d":[{"a":0,"b":"","c":true}]}]}

以上就是Moshi 完美解决Gson在kotlin中默认值空的问题详解的详细内容,更多关于Moshi解决Gson在kotlin默认值空的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
  • Android Jetpack组件Lifecycle源码解析
    Android Jetpack组件Lifecycle源码解析
    2023-03-03
  • Android封装Banner控件方法介绍
    Android封装Banner控件方法介绍
    2023-03-03
  • Android极光推送处理message遇到的坑解决
    Android极光推送处理message遇到的坑解决
    2023-02-02
  • Retrofit网络请求框架之注解解析和动态代理
    Retrofit网络请求框架之注解解析和动态代理
    2023-03-03
  • Moshi 完美解决Gson在kotlin中默认值空的问题详解
    Moshi 完美解决Gson在kotlin中默认值空的问题详解
    2023-03-03
  • 一文详解Jetpack Android新一代导航管理Navigation
    一文详解Jetpack Android新一代导航管理Navigation
    2023-03-03
  • 横竖屏切换导致页面频繁重启screenLayout解析
    横竖屏切换导致页面频繁重启screenLayout解析
    2023-03-03
  • Android 匿名内存深入分析
    Android 匿名内存深入分析
    2023-03-03
  • 美国设下计谋,用娘炮文化重塑日本,已影响至中国
    美国设下计谋,用娘炮文化重塑日本,已影响至中国
    2021-11-19
  • 时空伴随者是什么意思?时空伴随者介绍
    时空伴随者是什么意思?时空伴随者介绍
    2021-11-09
  • 工信部称网盘企业免费用户最低速率应满足基本下载需求,天翼云盘回应:坚决支持,始终
    工信部称网盘企业免费用户最低速率应满足基本下载需求,天翼云盘回应:坚决支持,始终
    2021-11-05
  • 2022年放假安排出炉:五一连休5天 2022年所有节日一览表
    2022年放假安排出炉:五一连休5天 2022年所有节日一览表
    2021-10-26
  • 电脑版 - 返回首页

    2006-2023 脚本之家 JB51.Net , All Rights Reserved.
    苏ICP备14036222号