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')

推荐

  1. 优先用 data class,框架内部专门针对 Kotlin data class 做了优化(通过 Kotlin 反射找主构造函数),普通 Kotlin 类只要有主构造函数也一样能走这条路径
  2. 基本类型建议给默认值而不是只写非空 :val name: String = ""、val age: Int = 0。只写非空(如 val name: String)虽然框架会兜底,但这属于作者「非常不推荐」的写法,并且依赖下面第 7 条的配置
  3. 集合用非空 :List<T>、Map<K,V> 不需要写 ?,后端返回 null、错误类型、甚至字段整个缺失时框架都会给一个空集合(只有写成 List<T>? 或者带默认值时,缺失才会是 null,见泛型章节)
  4. 善用默认值 :val name: String = "",框架会保留默认值不被 null 覆盖。只有写在主构造函数上、且该字段最终没有被赋值时才生效
  5. 嵌套对象和泛型字段用可空 :val msg: MsgInfo?、val data: T? = null,因为后端可能整个不返回这个字段。写成非空且无默认值时,泛型字段会被塞进 new Object(),使用时直接 ClassCastException
  6. 字段名和 JSON key 一致时不需要 @SerializedName,但加了也没坏处(防混淆)
  7. 如果用了「非空且无默认值」的写法,必须配置 android.enableR8.fullMode=false :AGP 8.6.0 及以上使用 R8 full mode 时,框架对该写法的兜底会失效(issues/51)。如果字段都有默认值或者都声明可空,则不需要加这行配置
  8. 顶层返回值要防 null :gson.fromJson<Student>(json, Student::class.java) 在 json 为 null 字符串时返回 null,赋给非空变量会触发 Kotlin 的空检查抛 NPE,建议用 ?: 兜底或者接收类型写 Student?
  9. 配合 setOnParseExceptionCallback 使用:容错会把后端的脏数据"吞掉",建议调试模式直接抛异常、线上模式上报(Bugly),避免问题被掩盖
  10. 接入 Retrofit :GsonConverterFactory.create(GsonFactory.getSingletonGson());需要自定义 Gson 时用 GsonFactory.newGsonBuilder(),不要自己 new GsonBuilder(),否则会丢失容错配置
相关推荐
WythePlus2 小时前
Android 14 SELinux策略深度解析
android·安全策略·selinux策略
俩个逗号。。6 小时前
Kotlin flow 详解
android·开发语言·kotlin
橙光成长8 小时前
android-ad-cleaner:android免 Root 批量关闭系统开屏与营销推送
android·1024程序员节
深圳雨林凯AI8 小时前
图案裂变的底色衰减:alpha通道、亮度对比与白墨分色的量化拆解
android
mmsx8 小时前
Android 模块化收口:能力组合框架、手写 LifecycleOwner 与热修复共存
android
恋猫de小郭10 小时前
Flutter 可以更直接地调用原生 API 了,Dart Direct Native Interop
android·前端·flutter
2501_9160088911 小时前
iPhone 设备信息查看,用 KeyMob 看型号、序列号、电池健康和 UDID
android·ios·小程序·https·uni-app·iphone·webview
好的佩奇21 小时前
蓝牙之通信架构
android·flutter·蓝牙
FlightYe21 小时前
视界原理之3D视频(四):裸眼、眼镜和头显
android·linux·c++·3d·ffmpeg·音视频