Android Kotlin Gson解析踩坑记录

一、背景

一般我们在进行网络请求拿到返回结果之后,我们期望能够转化成对应的Java实体类,在这个转化过程中,可以使用自动解析的方式,也可以使用三方提供的工具类,比如Gson、FastJson等。

针对于Gson解析,可能都有遇到某个字段类型不匹配导致整个json解析失败的问题,这不是我们期望的,我们期望的是一个字段解析失败不影响其他字段的解析。

那这种情况下怎么办呢?

下面就是踩坑记录和解决方案

二、问题 & 解决方案

1. 绕过Kotlin非空判断问题

问题描述
data class User(
        val name: String = "",  //String类型
        val age: Int,           //Kotlin Int类型,不加?会转换成java的int类型,加?会转换成Integer类型
        val bad: Boolean,       //Kotlin Boolean类型,不加?会转换成java的boolean类型,加?会转换成Boolean类型
        val relation: String = "",
        val friends: User,      //对象类型
        val family: List<User>  //数组类型
)
 
 
fun main() {
    val gson = GsonBuilder().create()
 
    val jsonStr = "{}"
    val user = gson.fromJson(jsonStr, User::class.java)
    println(user)
}

猜想一下上面打印出来的结果是什么?

答案是: User(name=null, age=0, bad=false, relation=null, friends=null, family=null)

可以看到我们声明的各种属性包含String类型,对象类型,数组类型,都是非空的,但是解析出来的结果却是null, 这并不符合我们的预期,如果这样的代码上线了,线上肯定崩溃无疑。

并且如果我们把age和bad属性添加?标识的话,我们将会得到下面的结果

User(name=null, age=null, bad=null, relation=null, friends=null, family=null)

所有的都变成null了。

那为什么会这样呢?针对这种情况,网上也能搜到对应文章说明
https://blog.csdn.net/lmj623565791/article/details/106631386

我们知道Kotlin data class类所有属性都赋初始值的时候,才会默认生成无参构造方法

由于上面的User类并没有所有的属性都赋初始值,最终导致生成的类没有无参的构造方法,而Kotlin data class的赋初始值的逻辑是在构造方法中实现的

Gson在没有找到默认构造方法时,它就直接通过Unsafe的方法,绕过了构造方法,直接构建了一个对象。

正是由于没走构造方法,所以默认值也不会被设置,最终数据的值就是Java中针对引用数据类型和基本数据类型对应的默认值了,即引用类型默认为null, 基本数据类型int 默认为0, boolean 默认为false

那在平常的开发过程中我们怎么避免此类问题发生呢?

解决方案
  1. data class的类所有属性都赋初始值,保证生成无参构造函数,使得默认值能够生效
  2. 对于引用数据类型尽量声明成可空类型,尽量确保程序可靠性
  3. 对于基本数据类型尽量声明成非可空类型,尽量减少内存消耗

所以针对上面的User类,我们应该尽量写成以下方式

//所有属性都赋初始值
@Keep  //保持不被混淆
data class User(
        val name: String? = "",
        val age: Int = 0,
        val bad: Boolean = false,
        val relation: String? = "",
        val friends: User? = null,
        val family: List<User>? = null
)

这样的话,上面打印出来的结果为:

User(name=, age=0, bad=false, relation=, friends=null, family=null)

另外一种解决方案就是:不使用data class, 而是直接使用class, 然后在类里面去声明对应的属性

这种情况下对应属性必须赋初始值,并且会默认有无参的构造方法

class User {
    private val name: String? = ""
    private val age: Int = 0
    private val bad: Boolean = false
    private val relation: String? = ""
    private val friends: User? = null
    private val family: List<User>? = null
 
    override fun toString(): String {
        return "User(name=$name, age=$age, bad=$bad, relation=$relation, friends=$friends, family=$family)"
    }
}

2. 类型不匹配导致解析失败问题

fun main() {
    val gson = GsonBuilder().create()
 
 
    val jsonStr = "{'name':'Coder','age':'aaa'}"    // 1
//  val jsonStr = "{'name':'Coder','friends':[]}"   // 2
//  val jsonStr = "{'name':'Coder','family':{}}"    // 3
    val user = gson.fromJson(jsonStr, User::class.java)
    println(user)
}

针对上面三种情况,会发生什么样的结果?
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
可以看到这3中情况都会导致异常,程序不能正常运行

但是实际情况中这种情况又不可能避免,服务端那边使用的是弱类型语言的话,这种情况发生的概率极大,端上不能完全依靠服务端去保证,不然crash率就会蹭蹭往上涨。

解决方案

自定义 TypeAdapterFactory

gson 库会通过JsonReader对json对象的每个字段进项读取,当发现类型不匹配时抛出异常

那么我们就在它抛出异常的时候进行处理,让它继续不中断接着往下读取其他的字段就好了

具体代码为:

public class GsonTypeAdapterFactory implements TypeAdapterFactory {
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        final TypeAdapter<T> adapter = gson.getDelegateAdapter(this, type);
        return new TypeAdapter<T>() {
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                adapter.write(out, value);
            }
 
            @Override
            public T read(JsonReader reader) throws IOException {
                try {
                    return adapter.read(reader);
                } catch (Throwable e) {
                    e.printStackTrace();
                    if (reader.hasNext()) {
                        JsonToken peek = reader.peek();
                        if (peek == JsonToken.NAME) {
                            reader.nextName();
                        } else {
                            reader.skipValue();
                        }
                    }
                    return null;
                }
            }
        };
    }
}
 
 
fun main() {
    val gson = GsonBuilder().registerTypeAdapterFactory(GsonTypeAdapterFactory()).create()  //gson创建的时候registerTypeAdapterFactory
 
    val jsonStr = "{'name':'Coder','age':'aaa'}"    // 1 
//  val jsonStr = "{'name':'Coder','friends':[]}"   // 2
//  val jsonStr = "{'name':'Coder','family':{}}"    // 3
    val user = gson.fromJson(jsonStr, User::class.java)
    println(user)
}

这种写法,上面三种情况都不会报错,并且最终解析的结果为:

User(name=Coder, age=0, bad=false, relation=, friends=null, family=null)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Kotlin 中,Gson 默认将 int 类型转换为 double 类型是因为 JSON 格式中没有 int 类型,只有数字类型。因此 Gson解析 JSON 数据时,将所有数字类型都解析为 double 类型。如果你想要将 int 类型保留为 int 类型,可以使用 GsonBuilder 在创建 Gson 对象时设置一个自定义的 TypeAdapter。 以下是一个示例,展示如何将 int 类型保留为 int 类型: ```kotlin val gson = GsonBuilder() .registerTypeAdapter(Int::class.java, object : TypeAdapter<Int>() { @Throws(IOException::class) override fun write(out: JsonWriter, value: Int?) { if (value == null) { out.nullValue() return } out.value(value) } @Throws(IOException::class) override fun read(`in`: JsonReader): Int { if (`in`.peek() == JsonToken.NULL) { `in`.nextNull() return 0 } return try { `in`.nextInt() } catch (e: NumberFormatException) { 0 } } }) .create() ``` 在这个示例中,我们创建了一个 GsonBuilder 对象,并注册了一个自定义的 TypeAdapter。这个 TypeAdapter 用于将 int 类型保留为 int 类型。在 write() 方法中,我们将 int 值写入 JsonWriter。在 read() 方法中,我们从 JsonReader 中读取 int 值。如果值为 null,则写入 null 值。如果读取的值不是 int 类型,则返回默认值 0。 此后,你可以使用这个 Gson 对象将 JSON 数据转换为对象,其中 int 类型将保留为 int 类型。例如: ```kotlin val jsonString = "{\"value\": 42}" val data = gson.fromJson(jsonString, Data::class.java) println(data.value) ``` 这里我们假设你有一个名为 Data 的数据类,它有一个 int 类型的属性 value。在这个示例中,我们将一个包含 int 类型值的 JSON 字符串反序列化为 Data 对象,并输出 value 属性的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值