前言
在日常开发中,处理 Json 格式的数据是一项常见的工作,无论是做什么类型的业务,在各种类型的 UI 之下,数据才是驱动一切进行的源头。随着技术的发展,在 Android 开发中用于 Json 解析的库也越来越多,甚至随着 Kotlin 和 Jetpack Compose 的发展,借助其语法糖和独特的开发方式,出现了专有的库。同时 Android 官方也提供对于数据序列化的更多支持。下面我们就一些常用的 Json 解析做一下比较,了解一下各自的特点,以便在日常开发中做出合适的选择。
以下举例以自己日常开发中常用或者用过的库为例
Json 解析
对于 Android 开发来说,Json 解析最有名的库莫过于 fastjson
了,相比系统自动的 Json 库提供了更友好的 API,方便开发者快速解析服务端返回的 Json 数据。尤其是在业务复杂的场景,比如电商类 App 的购物车、商品详情页、订单页这样的接口,返回的数据结构嵌套多,甚至可以说恐怖。而有了类似 fastjson
这样的库,只要定义好了数据类(也就是 JavaBean/JavaModel,从那个年代过来的人不同公司似乎有不同的叫法,但意思是相通的),一个简单的接口就可以快速将复杂的 json 字符串序列化为对象。同时,Google 官方也提供了 Gson 这样的库。 fastjson
也一度因为安全问题而被大家质疑和恐慌性的弃用。
而后,随着 Kotlin 成为主流,又出现了 Klaxon
,Moshi
这样基于 Kotlin 的 Json 库。同时,Android 官方也开始在这个方面发力,提供了 kotlinx.serialization
插件,通过给数据类添加注解的方式,非常友好的实现了数据解析的功能。
下面我们就结合实例总结和对比一下这些库各自的特点。
以下实例数据类结构如下
kotlin
class School {
class Location {
var lat = 0f
var lng = 0f
}
var name: String? = null
var province: String? = null
var city: String? = null
var area: String? = null
var address: String? = null
var location: Location? = null
}
data class Schools(
var id: Int = 0,
var province: String? = null,
var schoolList: List<School>? = null,
var type: Int = 0,
var code: String? = ""
)
这是一个全国各省市学校信息的数据,以省市为单位依次列出每个地区的所有学校地址。所有省市的 Schools 构成一个大的列表,原始 json 字符串大约有 5.6 MB,下面我们以反序列这个字符串为例依次对比一下上面提到的这些 Json 库的用法。
fastjson 和 Gson
fastjson 和 Gson 大家应该非常熟悉了,具体依赖就不再列出,简单看一下实现。
kotlin
private fun parseWithFastJson(json: String): List<Schools>? {
val s = System.currentTimeMillis()
val list = JSONObject.parseArray(json, Schools::class.java)
Log.e(TAG, "parseWithFastJson cost ${System.currentTimeMillis() - s}")
return list
}
private fun parseWithGson(json: String): List<Schools>? {
val s = System.currentTimeMillis()
val gson = Gson()
val list: List<Schools> = gson.fromJson(json, object : TypeToken<List<Schools>>() {}.type)
Log.e(TAG, "parseWithGson cost ${System.currentTimeMillis() - s}")
return list
}
这两者相对而言,fastjson 在速度和语法友好性上更占优。尤其对于列表的处理,Gson 的语法有点晦涩,记得当时刚开始学习 Kotlin 的时候,完全不知道该怎么写,因为原先 Java 的写法就很别扭,导致语法转换一直搞不定。
moshi
moshi 是由 Android 开发者非常熟悉的 square 出品的,就是那个搞出 okhttp
,retrofit
,leakcanary
的牛逼公司。
首先需要添加以下依赖
gradle
implementation("com.squareup.moshi:moshi:1.15.2") // 核心库
implementation("com.squareup.moshi:moshi-kotlin:1.15.2") // Kotlin 扩展(支持数据类)
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.15.2") // 代码生成(替代反射)
具体实现
kotlin
private fun parseWithMoshi(json: String) :List<Schools>? {
val s = System.currentTimeMillis()
val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory()) // 支持 Kotlin 数据类
.build()
val adapter = moshi.adapter<List<Schools>>(Types.newParameterizedType(List::class.java, Schools::class.java))
val list = adapter.fromJson(json)
Log.e(TAG, "parseWithMoshi cost ${System.currentTimeMillis() - s}")
return list
}
一看 moshi
的用法就会有一种似曾相识的感觉,square 一贯的风格,builder 模式,提供更多的灵活性,让使用者用配置的方式实现自己想要的结果。但是似乎又有些繁琐了,尤其是列表结构的常见,还得单独添加一下适配器。
klaxon
klaxon 似乎纯粹是为了借 kotlin 的东风,完全用 kotlin 实现的 Json 解析库,曾经名噪一时,但是已经停止维护了,这里列一下纯粹当做历史记录。
kotlin
private fun parseWithKlaxon(json: String): List<Schools>? {
val s = System.currentTimeMillis()
val list = Klaxon().parseArray<Schools>(json)
Log.e(TAG, "parseWithKlaxon cost ${System.currentTimeMillis() - s}")
return list
}
kotlinx.serialization
kotlinx.serialization 是 Kotlin 官方提供的序列化库,支持 JSON、Protobuf、CBOR 等多种格式,无需反射(通过编译时代码生成实现类型安全)
下面是他的用法
- 在 app/build.gradle 添加插件
gradle
plugins {
id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.10'
}
- 添加依赖
gradle
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
- 为数据类添加
@Serializable
注解
kotlin
@Serializable
data class Schools(
var id: Int = 0,
var province: String? = null,
var schoolList: List<School>? = null,
var type: Int = 0,
var code: String? = "aa"
)
这里当我们给 Schools
添加注解时,如果其内部含有未添加注解的类 Android Studio 就会爆红提示,比如这里的 List<School>
直到所有需要序列化的类都添加完毕。
- 解析数据
kotlin
fun parseWithKotlinxSerializable(jsonStr: String): List<Schools>? {
val s = System.currentTimeMillis()
val list = Json.decodeFromString<List<Schools>>(jsonStr)
Log.e("Coroutines", "parseWithKotlinxSerializable cost ${System.currentTimeMillis() - s}")
return list
}
可以看到其语法也很友好,见名知意,不需要额外的操作。
下面我们就来简单对比一下这几个实现的耗时。
kotlin
private fun useCoroutine2(block: (String) -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
Thread.currentThread().name.lg(TAG)
val start = System.currentTimeMillis()
val json = IOTool.readStrFromAssets("school.json", context)
Log.e(TAG, "read json cost ${System.currentTimeMillis() - start}")
block(json)
}
useCoroutine2 { parseWithKlaxon(it) }
useCoroutine2 { parseWithFastJson(it) }
useCoroutine2 { parseWithGson(it) }
useCoroutine2 { parseWithMoshi(it) }
useCoroutine2 { parseWithKotlinxSerializable(it) }
我们从 assets
目录读取 json 字符串之后,依次在 IO 线程调用通过参数传入的高阶函数来比较不同实现的差异。
shell
Coroutines E parseWithGson cost 1566
Coroutines E parseWithFastJson cost 3137
Coroutines E parseWithKotlinxSerializable cost 3414
Coroutines E parseWithMoshi cost 8761
Coroutines E parseWithKlaxon cost 78415
可以看到 Gson 最快,Klaxon 最慢,甚至慢的有些离谱,几乎是其他库的 10多倍(怪不得放弃维护了,估计没得救了)
但是号称最快的 fastJson 怎么败给了 Gson 呢?是哪里出了问题吗。这个问题,我们把数据类的定义从 kotlin 修改为 java 之后再看看
kotlin
private fun parseWithFastJson1(json: String): List<com.engineer.imitate.model.java.Schools>? {
val s = System.currentTimeMillis()
val list = JSONObject.parseArray(json, com.engineer.imitate.model.java.Schools::class.java)
Log.e(TAG, "parseWithFastJson1 cost ${System.currentTimeMillis() - s}")
return list
}
shell
Coroutines E parseWithFastJson1 cost 4547
Coroutines E parseWithGson cost 4903
Coroutines E parseWithFastJson cost 6523
Coroutines E parseWithKotlinxSerializable cost 7104
Coroutines E parseWithMoshi cost 12603
Coroutines E parseWithKlaxon cost 81603
可以看到改成 Java 之后 fastJson 明显变快了。当然,这其中的原因就是 Kotlin data class
导致的,由于构造函数的实现和 Java 有差异,所以在解析时需要用其他方式实现,因此会耗时一些。
当然,以上数据对比只能表现出单次结果的相对差异,并无法作为这些库性能优劣的指标 。但是,依然有参考意义。相对值之间差异较大的时候,其性能优劣就已经很明显了。日常开发实际使用时,用于序列化的 gson/json/moshi 实例应该尽可能的复用,而不是每次创建新的,这里只是为了方便举例。
综合对比
最后,我们用表格梳理一下上述几个库从更多维度再看看各自的差异。(借助 AI 生成, 准确性值得信赖)
维度 | Klaxon | Fastjson | Gson | Moshi | kotlinx.serialization |
---|---|---|---|---|---|
语言支持 | Kotlin | Java | Java/Kotlin(需适配) | Java/Kotlin(需扩展库) | Kotlin 原生 |
编译时安全 | ❌ 反射 | ❌ 反射 | ❌ 反射 | ✅ 可选(通过 kapt 代码生成) |
✅ 原生支持(无反射) |
空安全支持 | ✅ | ❌ | ❌(需额外配置) | ✅(需 Kotlin 扩展库) | ✅ 原生支持 |
默认字段名映射 | 支持(需注解) | 支持(@JSONField ) |
支持(@SerializedName ) |
支持(@Json ) |
支持(@SerialName ) |
性能(序列化) | 中等(≈1.5x Gson) | 极快(≈3x Gson) | 基准(1x) | 快(≈2x Gson,代码生成模式) | 中等(≈1.2x Gson) |
性能(反序列化) | 中等 | 极快 | 慢(≈0.8x Klaxon) | 快 | 中等 |
依赖大小 | 轻量(≈200KB) | 中等(≈500KB) | 轻量(≈250KB) | 轻量(≈300KB,含 Kotlin 扩展) | 轻量(≈200KB) |
多格式支持 | 仅 JSON | 仅 JSON | 仅 JSON | 仅 JSON(需插件) | ✅ JSON/Protobuf/CBOR 等 |
Kotlin 数据类支持 | ✅ 原生友好 | ❌(需手动处理) | ❌(需适配) | ✅(需 Kotlin 扩展库) | ✅ 原生完美支持 |
泛型支持 | ✅ | ✅ | ✅ | ✅(需复杂类型声明) | ✅ 原生支持 |
安全性 | 高(少历史漏洞) | 低(多次高危漏洞,需更新版本) | 高 | 高 | ✅ 最高(无反射) |
典型使用场景 | 纯 Kotlin 小项目 | Java 高性能后端 | Android/通用 Java 项目 | Android/Kotlin 项目 | Kotlin 全平台(KMM/Android) |
关键数据参考(基准测试) (源自网络)
测试环境:Kotlin 1.9 / JDK 17,数据集:1000 条嵌套 User
对象
(数值越低越好,Gson 为基准 1.0):
库 | 序列化时间 | 反序列化时间 | 内存占用 |
---|---|---|---|
Fastjson | 0.3x | 0.4x | 中等 |
Moshi (代码生成) | 0.5x | 0.6x | 低 |
kotlinx.serialization | 0.8x | 0.9x | 最低 |
Klaxon | 1.5x | 1.3x | 低 |
Gson | 1.0x | 1.0x | 中等 |
小结
通过以上内容和对比结果可以看到,没有最好的,只有最合适的。长远来说,在 Android 上 kotlinx.serialization
似乎是不错的选择,有官方支持。而且他还可以用于 protobuf 之类的其他语言,而且还有 Kotlin 全平台的支持,对于快平台开发来说也是一种优势。当然,实际业务处理非常复杂,需要结合自己的场景做出合理的选择。
参考
- Klaxon github.com/cbeust/klax...
- Fastjson github.com/alibaba/fas...
- Gson github.com/google/gson
- Moshi github.com/square/mosh...
- kotlinx.serialization github.com/Kotlin/kotl...