文章目录
- [Android GsonFactory总结](#Android GsonFactory总结)
Android GsonFactory总结
概述
Gson 解析容错框架:
https://github.com/getActivity/GsonFactory
添加仓库(Gradle 7.0 及以上写在 settings.gradle,7.0 以下写在根目录 build.gradle 的 allprojects 中):
dependencyResolutionManagement {
repositories {
// JitPack 远程仓库:https://jitpack.io
maven { url 'https://jitpack.io' }
}
}
添加依赖:
// Gson 解析容错:https://github.com/getActivity/GsonFactory
implementation("com.github.getActivity:GsonFactory:10.8")
// Json 解析框架:https://github.com/google/gson(版本要求 2.13.0 及以上)
implementation("com.google.code.gson:gson:2.14.0")
// Kotlin 反射库:用于反射 Kotlin data class 的主构造函数,版本号必须和项目的 Kotlin 插件版本一致
implementation("org.jetbrains.kotlin:kotlin-reflect:2.2.10")
注意事项:
-
项目必须要有 Kotlin 环境,并且 Gson 版本必须 >= 2.13.0,否则会出现版本兼容问题
-
升级 Gson 后如果出现 errorprone 编译失败,可以剔除该依赖:
implementation("com.google.code.gson:gson:2.14.0") {
exclude(group = "com.google.errorprone", module = "error_prone_annotations")
} -
开启混淆时必须保留 Bean 类的构造函数,否则反射 Kotlin data class 的构造函数会出现
ClassNotFoundException(xxx 换成 Bean 类所在的包名):-keepnames class xxx.xxx.xxx.** {
public(...);
}
JSON对象
数据类:
kotlin
data class Student(val name: String, val age: Int, val gender: Boolean)
正常值
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
{
"name":"小明",
"age":18,
"gender":true
}
""".trimIndent()
val bean = gson.fromJson<Student>(json, Student::class.java)
Log.e("TAG", bean.toString())
// Student(name=小明, age=18, gender=true)
空对象
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
{
}
""".trimIndent()
val bean = gson.fromJson<Student>(json, Student::class.java)
Log.e("TAG", bean.toString())
// Student(name=, age=0, gender=false)
null值
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
{
"name":null,
"age":null,
"gender":null
}
""".trimIndent()
val bean = gson.fromJson<Student>(json, Student::class.java)
Log.e("TAG", bean.toString())
// Student(name=, age=0, gender=false)
类型错误
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
{
"name":"",
"age":"",
"gender":""
}
""".trimIndent()
val bean = gson.fromJson<Student>(json, Student::class.java)
Log.e("TAG", bean.toString())
// Student(name=, age=0, gender=false)
JSON数组
正常值
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
[
{
"name":"小白",
"age":18,
"gender":true
},
{
"name":"小黑",
"age":19,
"gender":false
}
]
""".trimIndent()
val beans = gson.fromJson<List<Student>>(json, object : TypeToken<List<Student>>() {}.type)
Log.e("TAG", beans.toString())
// [Student(name=小白, age=18, gender=true), Student(name=小黑, age=19, gender=false)]
空数组
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
[
]
""".trimIndent()
val beans = gson.fromJson<List<Student>>(json, object : TypeToken<List<Student>>() {}.type)
Log.e("TAG", beans.toString())
// []
null值
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
[
{
"name":null,
"age":null,
"gender":null
}
]
""".trimIndent()
val beans = gson.fromJson<List<Student>>(json, object : TypeToken<List<Student>>() {}.type)
Log.e("TAG", beans.toString())
// [Student(name=, age=0, gender=false)]
类型错误
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
[
{
"name":"",
"age":"",
"gender":""
}
]
""".trimIndent()
val beans = gson.fromJson<List<Student>>(json, object : TypeToken<List<Student>>() {}.type)
Log.e("TAG", beans.toString())
// [Student(name=, age=0, gender=false)]
泛型
kotlin
data class Response<T>(val code: Int, val message: String, val data: T? = null)
泛型字段必须写成 T? 并且给默认值(原因见本章最后的「错误写法」)。
正常值
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
{
"code": 1,
"message": "hello",
"data": {
"name": "小明",
"age": 18,
"gender": true
}
}
""".trimIndent()
val bean = gson.fromJson<Response<Student>>(json, object : TypeToken<Response<Student>>() {}.type)
Log.e("TAG", bean.toString())
// Response(code=1, message=hello, data=Student(name=小明, age=18, gender=true))
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
{
"code": 1,
"message": "hello",
"data": [
{
"name": "小明",
"age": 18,
"gender": true
},
{
"name": "小白",
"age": 19,
"gender": false
}
]
}
""".trimIndent()
val bean =
gson.fromJson<Response<List<Student>>>(json, object : TypeToken<Response<List<Student>>>() {}.type)
Log.e("TAG", bean.toString())
// Response(code=1, message=hello, data=[Student(name=小明, age=18, gender=true), Student(name=小白, age=19, gender=false)])
字段缺失
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
{
"code": 1,
"message": "hello"
}
""".trimIndent()
val bean = gson.fromJson<Response<Student>>(json, object : TypeToken<Response<Student>>() {}.type)
Log.e("TAG", bean.toString())
// Response(code=1, message=hello, data=null)
data 字段整个缺失时,框架不会给该字段赋值,最终保留主构造函数上的默认值 null。
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
{
"code": 1,
"message": "hello"
}
""".trimIndent()
val bean =
gson.fromJson<Response<List<Student>>>(json, object : TypeToken<Response<List<Student>>>() {}.type)
Log.e("TAG", bean.toString())
// Response(code=1, message=hello, data=null)
data 的类型虽然是 List<Student>,但字段缺失时走的同样是「不赋值、保留默认值」这条路,所以拿到的是 null 而不是空集合(和下面 null 值的结果不同,需要自己判空)。
null值
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
{
"code": 1,
"message": "hello",
"data": null
}
""".trimIndent()
val bean = gson.fromJson<Response<Student>>(json, object : TypeToken<Response<Student>>() {}.type)
Log.e("TAG", bean.toString())
// Response(code=1, message=hello, data=null)
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
{
"code": 1,
"message": "hello",
"data": null
}
""".trimIndent()
val bean =
gson.fromJson<Response<List<Student>>>(json, object : TypeToken<Response<List<Student>>>() {}.type)
Log.e("TAG", bean.toString())
// Response(code=1, message=hello, data=[])
集合类型比较特殊:data 为 null 时,集合适配器返回的是一个空集合(不是 null),所以这里拿到的是 []。
错误写法:非空且无默认值的泛型字段
如果把 data 写成下面这样(非空、无默认值):
kotlin
data class Response<T>(val code: Int, val message: String, val data: T)
那么当 data 字段缺失时(或者为 null 且不是集合类型时),框架会用 Kotlin 主构造函数兜底创建 Response,而 T 在没有边界的情况下会被擦除成 java.lang.Object,于是框架反射 new 了一个 Object 塞进 data:
kotlin
// Response(code=1, message=hello, data=java.lang.Object@a595938)
此时 Response<Student>.data 在 Kotlin 侧的静态类型是 Student,一旦访问就会 ClassCastException。所以泛型字段要么写成 T? = null,要么直接使用具体类型。
默认值
默认值的生效时机:需要满足"默认值在主构造函数上"+"该字段没有最终被赋值"。
框架的流程是先用 Kotlin 反射找到主构造函数并调用它创建对象,再把 JSON 里出现的字段反射写进去。因此:
- 写在主构造函数参数 上、且带有默认值的参数(
isOptional),框架会用callBy让 Kotlin 自己去填默认值 - 如果 JSON 里出现了这个字段并且值合法,后面反射写入会覆盖掉默认值
- 没有默认值且非空 的参数,框架填的是"类型零值"(
String→""、Int→0、Boolean→false、对象 → 反射创建一个新对象),也就是「兜底值」,不是你以为的默认值 - 类体里的属性(如下面的
city)也会在构造时初始化,但同样会被 JSON 中的同名 key 覆盖
kotlin
data class Student(val name: String = "小白", val age: Int = 18, val gender: Boolean = true) {
val city = "beijing"
override fun toString(): String {
return "Student(name='$name', age=$age, gender=$gender, city='$city')"
}
}
kotlin
val gson: Gson = GsonFactory.getSingletonGson()
val json = """
{
}
""".trimIndent()
val bean = gson.fromJson<Student>(json, Student::class.java)
Log.e("TAG", bean.toString())
// Student(name='小白', age=18, gender=true, city='beijing')
推荐
- 优先用 data class,框架内部专门针对 Kotlin data class 做了优化(通过 Kotlin 反射找主构造函数),普通 Kotlin 类只要有主构造函数也一样能走这条路径
- 基本类型建议给默认值而不是只写非空 :
val name: String = ""、val age: Int = 0。只写非空(如val name: String)虽然框架会兜底,但这属于作者「非常不推荐」的写法,并且依赖下面第 7 条的配置 - 集合用非空 :
List<T>、Map<K,V>不需要写?,后端返回 null、错误类型、甚至字段整个缺失时框架都会给一个空集合(只有写成List<T>?或者带默认值时,缺失才会是null,见泛型章节) - 善用默认值 :
val name: String = "",框架会保留默认值不被 null 覆盖。只有写在主构造函数上、且该字段最终没有被赋值时才生效 - 嵌套对象和泛型字段用可空 :
val msg: MsgInfo?、val data: T? = null,因为后端可能整个不返回这个字段。写成非空且无默认值时,泛型字段会被塞进new Object(),使用时直接ClassCastException - 字段名和 JSON key 一致时不需要
@SerializedName,但加了也没坏处(防混淆) - 如果用了「非空且无默认值」的写法,必须配置
android.enableR8.fullMode=false:AGP 8.6.0 及以上使用 R8 full mode 时,框架对该写法的兜底会失效(issues/51)。如果字段都有默认值或者都声明可空,则不需要加这行配置 - 顶层返回值要防 null :
gson.fromJson<Student>(json, Student::class.java)在 json 为null字符串时返回 null,赋给非空变量会触发 Kotlin 的空检查抛 NPE,建议用?:兜底或者接收类型写Student? - 配合
setOnParseExceptionCallback使用:容错会把后端的脏数据"吞掉",建议调试模式直接抛异常、线上模式上报(Bugly),避免问题被掩盖 - 接入 Retrofit :
GsonConverterFactory.create(GsonFactory.getSingletonGson());需要自定义 Gson 时用GsonFactory.newGsonBuilder(),不要自己new GsonBuilder(),否则会丢失容错配置