我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Moshi如何解决Gson在kotlin中默认值空问题

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Moshi如何解决Gson在kotlin中默认值空问题

本文小编为大家详细介绍“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 == constructorSizefor (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中默认值空问题”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    Moshi如何解决Gson在kotlin中默认值空问题

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

    Moshi如何解决Gson在kotlin中默认值空问题

    本文小编为大家详细介绍“Moshi如何解决Gson在kotlin中默认值空问题”,内容详细,步骤清晰,细节处理妥当,希望这篇“Moshi如何解决Gson在kotlin中默认值空问题”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来
    2023-07-05

    在 Golang 中保存到 MongoDB 时如何解决空数据值问题?

    在Golang中,保存数据到MongoDB时,经常会遇到空数据值的问题。当某个字段的值为空时,MongoDB默认会将其存储为null。然而,在实际应用中,我们可能更希望将其存储为一个特定的默认值,以便更好地处理和查询数据。那么,在Golan
    在 Golang 中保存到 MongoDB 时如何解决空数据值问题?
    2024-02-09

    如何解决Ubuntu中修复默认启用HDMI后没有声音的问题

    本篇内容主要讲解“如何解决Ubuntu中修复默认启用HDMI后没有声音的问题”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何解决Ubuntu中修复默认启用HDMI后没有声音的问题”吧!声音问题
    2023-06-16

    如何解决网页设计中Flash默认总是会显示在页面的最顶层的问题

    这篇文章主要讲解了“如何解决网页设计中Flash默认总是会显示在页面的最顶层的问题”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何解决网页设计中Flash默认总是会显示在页面的最顶层的问题
    2023-06-08

    如何在java中解决main函数中的args数组传值问题

    这篇文章将为大家详细讲解有关如何在java中解决main函数中的args数组传值问题,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Java可以用来干什么Java主要应用于:1. web开发;
    2023-06-06

    在Go语言中如何解决并发网络请求的请求身份认证和访问授权问题?

    在Go语言中如何解决并发网络请求的请求身份认证和访问授权问题?随着互联网的快速发展,越来越多的应用需要进行网络请求,涉及到用户身份认证和访问授权问题。如何在Go语言中解决并发网络请求的请求身份认证和访问授权问题,成为了开发者们面临的一项重要
    2023-10-22

    在Go语言中如何解决并发网络请求的请求认证和授权处理问题?

    在Go语言中如何解决并发网络请求的请求认证和授权处理问题?随着互联网的高速发展,网络请求在我们的日常开发中扮演着非常重要的角色。然而,随着系统规模的扩大和并发量的增加,请求认证和授权问题也逐渐变得复杂起来。在这篇文章中,我们将探讨在Go语言
    2023-10-22

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录