gson反序列化成data class时的坑

2023-11-16

前言

在Android开发中,gson是很常用的用来处理json的三方库,它是由Google维护的,一直以来都比较稳定,至少在使用Java开发时是这样的。

但是,gson对Kotlin的data class的支持就不是很完善了,会有一些坑,下面我们来看一看


gson反序列化成data class的正常情况

在kotlin中我们使用data class来充当数据类,举个例子:

data class User(
    val name: String,
    val age: Int,
)

使用gson将json解析成对象也非常简单,如下

fun main() {
    val jsonStr = """{"name":"喻志强","age":0}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${user}")
}

在这里插入图片描述

然后我们就得到一个User对象,数据也是ok的。之前也写过一些关于使用gson时的小技巧和封装,感兴趣的可以看一下:
Gson使用的一些小技巧
以上情况是建立在json数据正常的情况下。但json数据不那么正常时,坑就产生了,我们来看一下


属性值不能为null,结果反序列化后出现了null值

还是上面的例子,data class如下

data class User(
    val name: String,
    val age: Int,
)

如果json数据中的name没有了,如下

fun main() {
    val jsonStr = """{"age":0}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${user}")
}

解析出来的对象如下
在这里插入图片描述
一眼看上去这没毛病啊,都没有name字段,那name的值可不就是null吗?

然后在写代码时如果你稍微不注意,写了类似对name操作的代码,例如

fun main() {
    val jsonStr = """{name:null,age:18}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${user}")
    val substring = user.name.substring(0, 1)
    /*获取name的第一个字符*/
    println("substring = ${substring}")
}

运行
在这里插入图片描述
可不就报错吗?name是null啊,但是仔细一想这不对啊,这显然跟kotlin的语法产生了冲突,我们都知道,在data class中如果一个属性的值可以为null,我们需要用?来标识,如下

在这里插入图片描述
那这样在写代码时如果对name进行操作时,IDE会提示我们加上问号,这样可以有效避免出现空指针异常
在这里插入图片描述
加上?号运行一下,确实不报错了。
在这里插入图片描述
但是我们data class中的name没有加问号时也会出现null值,这就是第一个坑,跟预期不符。
原因是gson使用的是java的反射去构建的对象,也就是说gson并不认识data class,也就不会去满足kotlin空安全的特性,这样一来很容易出现奇怪的空指针异常。

解决该问题的方式很简单,就是给name加个?就好了,我个人是很不喜欢用空安全这个问号的,写起来有点恶心了,每个地方都要加问号,虽然可以有效避免空指针异常,而且在一些业务场景下,某些字段是必须要有值的,本来就不能为null,如果为null说明数据是有异常的,在反序列化时直接抛异常就好了。
gson的坑就在于无法在反序列化时就告诉你数据异常,只有到用到这个字段的值时,才会出现NPE,这个时候就晚了…


什么?data class默认值没有生效?

有些场景下,我们希望数据有一些默认值,或者说我们为了解决上面那个null值的问题,想着给name一个默认值,期望如果json中没有name这个字段的话,就用name的默认值好了,如下:

data class User(
    val name: String="喻志强",
    val age: Int,
)

然后再运行看一下

fun main() {
    val jsonStr = """{age:28}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${user}")
    val substring = user.name.substring(0, 1)
    /*获取name的第一个字符*/
    println("substring = ${substring}")
}

依旧报错
在这里插入图片描述

可以发现默认值并没有生效,仍然是null,打个断点你就知道原因是gson在找User的无参构造时没有找到,最终通过UnsafeAllocator.create()直接创建对象,根本没有走构造,构造都没走,给的默认值自然就不生效了
在这里插入图片描述

那解决这个的问题也很简单,我们给每个属性都赋一个初始值,这样就会生成一个无参的构造函数了,也就不会出现这种问题了,来试一下

data class User(
    val name: String="喻志强",
    val age: Int=0,
)

再次运行

fun main() {
    val jsonStr = """{age:18}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${user}")
    val substring = user.name.substring(0, 1)
    println("substring = ${substring}")
}

在这里插入图片描述

恩,这样好像就ok了,这个也是我自己在日常开发中的方式,我习惯给每个属性一个默认值,写代码的时候好处理一些。
但是给上默认值就万无一失了吗?当然不是
再来看下下面的代码

fun main() {
    val jsonStr = """{name:null,age:18}"""
    val gson = GsonBuilder().create()
    val user = gson.fromJson(jsonStr, User::class.java)
    println("user = ${user}")
    val substring = user.name.substring(0, 1)
    println("substring = ${substring}")
}

我们已经给data class所有属性默认值了,但是当json中某个属性的值显式的为null时,null值还是会覆盖到原本的默认值,这就又掉进第一个坑了,声明的时候不能为null,写代码的时候也没异常提示,结果运行就会出现NPE。

像这种返回数据不规范的情况还真没啥特别好的办法处理,唯一好处理的办法就是跟后端撕逼叫后端改…

在这里插入图片描述
不过我还找到了更好的解决办法,自己写后端,学一学crud就足够了,不用求人,我就是这样…

在这里插入图片描述

好了,不装了,总结一下gson结合data class时可能产生的两个主要问题吧

  • 属性声明时值不能为null,结果反序列化后值为null,跟预期不符
  • 默认值可能不生效,可能被null覆盖

其实针对上面的问题也有一些解决办法,自行查一下吧,但是不太喜欢,因为没有从根源解决问题。

要想从根源解决,很简单,不要用gson了,换moshi或者jackson就可以了,他们都针对kotlin做了单独的处理,具体用法官方文档也都有,这里先放出github地址。

moshi:https://github.com/square/moshi

Jackson:jackson-module-kotlin

下篇博客是对moshi的基础使用及实战,感兴趣的可以了解一下

对kotlin友好的现代 JSON 库 moshi 基本使用和实战


如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

gson反序列化成data class时的坑 的相关文章

  • Android:自定义Toast通知继承默认Toast

    我有一个自定义的 Toast 通知 其中包含图像和文本 自定义 toast 工作正常 但我想知道如何使我的自定义 toast 继承默认 toast 的外观和感觉 我希望它看起来像默认的那样 具有漂亮的圆角和边框 这就是我定制的吐司的样子
  • SpinnerAdapter 中 getView 和 getDropDownView 的区别

    当你实现 SpinnerAdapter 时 你会得到获取下拉视图 http developer android com reference android widget SpinnerAdapter html getDropDownView
  • 我可以为 Android Activity 分配“默认”OnClickListener() 吗?

    我有一个 Activity 对于布局中的每个小部件 我调用 setOnClickListener 来分配我的 OnClick 处理程序 在我的 OnClick 处理程序中 我使用 switch 语句根据 View 参数的 ID 为每个按钮执
  • 在 Java/Android 中检查字符串是否包含 URL 的最佳方法是什么?

    在 Java Android 中检查字符串是否包含 URL 的最佳方法是什么 最好的方法是检查字符串是否包含 com net org info 其他 或者有更好的方法吗 url 输入到 Android 中的 EditText 中 它可以是粘
  • 411 需要内容长度

    我正在尝试使用 Android Apache HttpClient 执行 POST 但它返回错误 411 Content Length required 这是代码 HttpClient httpClient new DefaultHttpC
  • 相机预览的有效模糊

    到目前为止我尝试过的 将每一帧转换为位图 然后用library https github com wasabeef Blurry并将其放入ImageView这是在相机预览前 显然太慢了 就像1 fps 然后我开始使用渲染脚本这会模糊每一帧
  • 我无法从 Android 模拟器中删除日语 IME

    我已经多次看到这个问题 但答案总是 从 语言和键盘设置 菜单中取消选中 IME 问题是那里没有复选框 选择菜单 自定义区域设置 en US 菜单 设置 语言和键盘 选择语言 选择 英语 美国 菜单 设置 语言和键盘 日语输入法是唯一的输入法
  • 删除所有(子)片段的正确方法

    我在父级片段线性布局 fragmentContainer 中动态加载一堆子级片段 然后当用户单击按钮时 我需要将它们全部删除并添加新的 我不知道每次会添加多少碎片 这是我一次性删除所有碎片的方法 LinearLayout ll Linear
  • 使用 Kotlin 协程替换 LocalBroadcastManager 以进行 Firebase 消息传递

    使用时Firebase 云消息传递 https firebase google com docs cloud messaging android client在 Android 上 通常需要通知当前Activity传入的推送通知 推荐的方法
  • 如何在Android项目中使用libffmpeg.so?

    我正在尝试在 Android 中创建一个屏幕录制应用程序 为此 我使用 FFmpeg 我已经创建了 libffmpeg so 文件 现在我想在 Android 项目中使用相同的方法来调用它的本机函数 我怎样才能做到这一点 本教程提供了有关此
  • 如何使用android ndk r9b为Android编译FFMPEG

    我想设计一个Android应用程序 可以通过FFMPEG命令播放和编辑视频 但我不知道如何在Android上使用FFMPEG 我尝试过从Google搜索到的许多方法 但它们太旧了 无法实现 现在 FFMPEG的最新版本是2 1 1 Andr
  • 没有 Listview 的 Android 导航抽屉

    我想创建一个像导航抽屉一样的滑动菜单 但是将会有文本视图和图像视图 就像半活动一样 有可能做到这一点吗 您可以使用NavigationDrawer来自支持库 包括一个FrameLayout作为一个孩子DrawerLayout并使androi
  • setOnTouchListener() 给我一个错误

    button setOnTouchListener new OnTouchListener public void onClick View v Toast makeText MainActivity this YOUR TEXT 5000
  • Android Studio 3.0 - 设置未保存

    我已将 文件 gt 设置 gt 编辑器 gt 代码样式 中的 右边距 列 从默认的 100 增加到 140 不幸的是 每次重新启动 Android Studio 后 该边距都会重置 我还尝试导出和导入我的设置 但这并不能阻止重置右边距 希望
  • 如何在Android JUnit测试用例中调用Button.performClick?

    我是 Android 测试方面的新手 我想测试单击按钮是否会打开相应的活动 我做了一些研究 发现我需要使用 ActivityManager 来进行检查 问题是 我无法让 点击 部分正常工作 我正在尝试使用Button performClic
  • IllegalStateException:无法更改片段的标签,以前是 android:switcher,现在是 android:switcher

    我的活动使用TabLayout ViewPager 这里的选项卡和页面的数量是动态的 具体取决于从服务器获取的数据 崩溃是通过 Crashlytics 报告的 我无法复制它 我的活动代码 Override protected void on
  • 在 WebView 中打开 PDF 文件

    大约 2 天 我尝试在我的自定义中打开 PDF 文件WebvView 这是我的WebView code import android app AlertDialog import android app ProgressDialog imp
  • 如何使用Android Invalidate()

    在我的主要活动中 我定义了两个视图和一个菜单 浏览次数 1 自定义视图游戏 2 按钮btn 菜单 1 打开项目用于打开文件 菜单布局在不同的活动中定义 基本上 当主活动启动时 它会绘制没有任何内容的自定义视图和按钮 然后我使用菜单中的 打开
  • 智能手机可以通过 3G/4G 进行点对点通信吗?

    我正在尝试编写一个应用程序 将数据从一个 Android 设备传输到另一个 Android 设备 但这些设备很可能位于城市 州或国家的不同部分 直接的方法是拥有一台中央服务器 或任何类型的服务器 但我试图避免使用中央服务器 我试图传递的数据
  • 如何在不显示父活动的情况下将一个全屏对话框片段替换为另一个全屏对话框片段?

    我有一个使用单个自定义 DialogFragment 类的活动 它的外观是数据驱动的 因此不同的调用看起来可能相当不同 它是 全屏 即 setStyle DialogFragment STYLE NO FRAME android R sty

随机推荐

  • unity 加载场景时加载失败的问题

    需要实现场景跳转的功能 而且需要实现跳转的场景不是一个 其中一个可以很好的跳转 但是另一个新建的场景在跳转时却报错 Scene BatteryMaintenance couldn t be loaded because it has not
  • Apache PLC4X 副总裁宣布个人停止对项目提供免费支持;Linux 5.17 增加对中国 Soc 的支持;IPython 8.0 发布

    整理 宋彤彤 责编 屠敏 开源吞噬世界的趋势下 借助开源软件 基于开源协议 任何人都可以得到项目的源代码 加以学习 修改 甚至是重新分发 关注 开源日报 一文速览国内外今日的开源大事件吧 一分钟速览新闻点 开源大新闻 因缺少资金 Apach
  • 网络工程毕业设计选题大全 毕设题目推荐

    文章目录 0 简介 1 如何选题 2 最新网络工程选题 2 1 Java web SSM 系统 2 2 大数据方向 2 3 人工智能方向 2 4 其他方向 4 最后 0 简介 学长搜集分享最新的网络工程专业毕设毕设选题 难度适中 适合作为毕
  • Aspose.Diagram for Java V22.5

    Aspose Diagram for Java V22 5 Aspose Diagram for Java 是一个强大的 Microsoft Visio 文件处理 API 它提供了通用功能 例如创建 操作和转换本机 Visio 格式以及一些
  • 大数据学习脑图以及容易消化的入门教程

    近些年 大数据的火热可谓是技术人都知道啊 很多人呢 也想学习大数据相关 所以 这里分享几个大数据脑图 希望可以让你清楚明白从哪里入门大数据 知道该学习以及掌握哪些知识点 大数据相关脑图 想要在大数据这个领域汲取养分 让自己壮大成长 分享方向
  • sklearn中cross_val_score、cross_val_predict的用法比较

    交叉验证的概念 直接粘贴scikit learn官网的定义 scikit learn中计算交叉验证的函数 cross val score 得到K折验证中每一折的得分 K个得分取平均值就是模型的平均性能 cross val predict 得
  • 排列组合理解SQL JOINS的几种情况

    一 JOIN的三种方式 1 left join 2 right join 3 full join 二 Join的结果 两个集合的join可能出现多少中结果呢 利用数学里的排列组合知识很容易算出来 如上图 join相当于把两个集合分为三个部分
  • opencv光流Optical Flow

    光流Optical Flow 现在四轴飞行器越来越火 如何在室内进行定位呢 不同于传统四轴的姿态控制 电机驱动 室外定位 都有了一套完整的方案 室内定位还是没有完全成熟 目前大四轴可以利用的GPS定高 小四轴比较成熟的也就是光流方案了 先看
  • springboot之接受数据的三种方式:@requestParam,@requestBody和@PathVariable

    前言 作为后端 我们经常需要和前端进行数据之间的交互 而我们从前端获取的方式主要有下面几种 路径中的数据 例如localhost user 2 这时我们需要获得路径上的数字2 存放到url头里的参数例如localhost user name
  • pytorch入门的入门

    DATASETS DATALOADERS 两个有用的 torch utils data DataLoader and torch utils data Dataset 其中Dataset存储样本和标签 就是图片和真值 而DataLoader
  • JDK7下载

    JDK7下载 JDK1 7下载可选择window版和linux版 下载说明文档 判断系统是64位还是32位 Linux下选tar gz好还是rpm 1 windows版JDK1 7 64位下载 点击下载 jdk 7u67 windows x
  • MATLAB/Simulink 使用记录

    1 InitFcn 仿真模块全局变量初始化 启动Run之后可以添加至工作区 右键 Model Perprities Callbacks InitFcn 2 子模块参数输入 右键模块 Mask Edit Mask Parameter Dial
  • java 中各种数据类型的互相转换的常用方法

    java 各种数据类型的互相转换 1 StringBuilder转化为String String str abcdefghijklmnopqrs StringBuilder stb new StringBuilder str 2 整型数组转
  • openCV 3.4.7在Visual Studio 2015中配置

    准备 1 安装openCV3 4 7 https opencv org releases 2 安装visual studio 2015 http c biancheng net view 453 html 开始配置 1 计算机 右键 属性
  • 北邮22级信通院数电:Verilog-FPGA(3)实验“跑通第一个例程”modelsim仿真及遇到的问题汇总(持续更新中)

    北邮22信通一枚 跟随课程进度更新北邮信通院数字系统设计的笔记 代码和文章 持续关注作者 迎接数电实验学习 获取更多文章 请访问专栏 北邮22级信通院数电实验 青山如墨雨如画的博客 CSDN博客 注意 本篇文章所有绝对路径的展示都来自上一篇
  • C语言回调函数学习

    作者 杨硕 华清远见嵌入式学院讲师 对指针的应用是C语言编程的精髓所在 而回调函数就是C语言里面对函数指针的高级应用 简而言之 回调函数是一个通过函数指针调用的函数 如果你把函数指针 函数的入口地址 传递给另一个函数 当这个函数指针被用来调
  • FBX SDK 开发环境配置 visual studio 2022

    FBX Adaptable File Formats for 3D Animation Software Autodesk 下载windows的sdk并安装 创建一个c console 工程 设置include目录 添加预处理宏 FBX S
  • 【前端】html+js+css开发入门超详细介绍

    文章目录 一 HTML 1 1 第一个页面 1 2 所有标签都来一遍 1 3 超链接 1 4 发邮件 1 5 description list描述列表 1 6 blockquote块引用 1 7 linequote 1 8 address
  • dns改成什么网速快_这个DNS服务器不仅更快而且安全

    DNS也就是域名解析服务器 这个东西的存在 使我们上网变得非常方便 再也不需要去记下复杂的IP了 而同样 DNS也影响着我们的网速 那么今天 小编就给大家推荐一个DNS服务器 这个DNS服务器不仅更快 而且更加安全 一起来看看吧 中国互联网
  • gson反序列化成data class时的坑

    前言 在Android开发中 gson是很常用的用来处理json的三方库 它是由Google维护的 一直以来都比较稳定 至少在使用Java开发时是这样的 但是 gson对Kotlin的data class的支持就不是很完善了 会有一些坑 下