Android Kotlin Gson解析踩坑记录

  • Post author:
  • Post category:其他




一、背景

一般我们在进行网络请求拿到返回结果之后,我们期望能够转化成对应的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)



版权声明:本文为qq_33666539原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。