Android Json 解析你还在用 fastjson 吗?

前言

在日常开发中,处理 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 等多种格式,​​无需反射​​(通过编译时代码生成实现类型安全)

下面是他的用法

  1. 在 app/build.gradle 添加插件
gradle 复制代码
plugins {
    id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.10'
}
  1. 添加依赖
gradle 复制代码
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
  1. 为数据类添加 @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> 直到所有需要序列化的类都添加完毕。

  1. 解析数据
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 全平台的支持,对于快平台开发来说也是一种优势。当然,实际业务处理非常复杂,需要结合自己的场景做出合理的选择。

参考

相关推荐
alexhilton6 小时前
Android ViewModel数据加载:基于Flow架构的最佳实践
android·kotlin·android jetpack
小阳睡不醒8 小时前
小白成长之路-develops -jenkins部署lnmp平台
android·运维·jenkins
踏雪羽翼9 小时前
Android 接入deepseek
android
whatever who cares9 小时前
Android/Java 异常捕获
android·java·开发语言
火车叼位9 小时前
Realm数据库Schema迁移终极指南:从入门到生产环境
android
初始化9 小时前
Android 页面代码粒度化管理进阶
android·kotlin
Digitally10 小时前
66最佳红米手机数据擦除软件
android
xiayiye511 小时前
Android开发之fileprovider配置路径path详细说明
android·fileprovider·android path配置·fileprovider配置