JSON 解析
本教程共 100 篇 · 第 72 篇 · 更新于 2026-07-28 · 约 8 分钟阅读
72. JSON 解析
本节目标:搞懂 Android 端 JSON 解析的几种方案,会用 Moshi/Gson 把复杂 JSON 映射成 Kotlin 对象,会处理嵌套结构和列表,也会手动用 JSONObject 解析不确定结构。
后端返回的数据 99% 是 JSON。怎么把它变成 Kotlin 对象,是网络编程的核心环节。这章讲透。
JSON 长什么样
JSON 就两种结构:
- 对象:
{ "key": value },对应 Kotlin 的对象。 - 数组:
[ item, item ],对应 Kotlin 的 List。
值可以是字符串、数字、布尔、null,也可以嵌套对象或数组。比如:
{
"id": 1,
"name": "张三",
"active": true,
"address": {
"city": "北京",
"zip": "100000"
},
"hobbies": ["读书", "写代码"]
}
对应 Kotlin:
data class User(
val id: Int,
val name: String,
val active: Boolean,
val address: Address,
val hobbies: List<String>
)
data class Address(
val city: String,
val zip: String
)
字段类型对得上就行。
两个主流库:Moshi 和 Gson
Gson
Google 出的老牌库,几乎人人在用:
dependencies {
implementation("com.google.code.gson:gson:2.11.0")
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
}
用法:
val gson = Gson()
// 对象转 JSON
val json = gson.toJson(user)
// JSON 转对象
val user = gson.fromJson(json, User::class.java)
Moshi
Square 出的,对 Kotlin 更友好:
dependencies {
implementation("com.squareup.moshi:moshi:1.15.1")
implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
implementation("com.squareup.retrofit2:converter-moshi:2.11.0")
// KSP 生成代码
ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.1")
}
用法:
val moshi = Moshi.Builder().build()
// 对象转 JSON
val json = moshi.adapter(User::class.java).toJson(user)
// JSON 转对象
val user = moshi.adapter(User::class.java).fromJson(json)
选哪个
| 维度 | Gson | Moshi |
|---|---|---|
| Kotlin 可空类型 | 容易踩坑,null 能塞进非空字段 | 严格校验,违反就抛异常 |
| 默认值 | 不支持 | 支持 |
| 性能 | 反射,慢 | KSP 生成代码,快 |
| 生态 | 老项目都在用 | 新项目首选 |
| 学习成本 | 低 | 低 |
我的建议:新项目用 Moshi + KSP,老项目继续 Gson。本教程主线用 Moshi。
WarningGson 在 Kotlin 里有个坑:构造函数的非空字段,如果 JSON 里没这个 key 或者值是 null,Gson 用反射创建对象时会塞 null 进去,绕过 Kotlin 的非空检查,运行时一访问就 NPE。Moshi 不会。
字段映射
JSON 的 key 和 Kotlin 字段名不一致时,要映射。
Moshi
data class User(
@Json(name = "user_id") val id: Int,
@Json(name = "user_name") val name: String
)
Gson
data class User(
@SerializedName("user_id") val id: Int,
@SerializedName("user_name") val name: String
)
嵌套解析
JSON 里有嵌套对象,Kotlin 数据类也跟着嵌套就行:
{
"user": {
"name": "张三",
"profile": {
"age": 28,
"avatar": "https://..."
}
},
"token": "abc123"
}
data class LoginResponse(
val user: User,
val token: String
)
data class User(
val name: String,
val profile: Profile
)
data class Profile(
val age: Int,
val avatar: String
)
Retrofit + Moshi 会自动层层解析,不用你管。
Tip嵌套太深的 JSON 建议和后端商量拆分,前端解析麻烦,传输也浪费。
列表解析
JSON 是数组时,Kotlin 用 List<T>:
[
{ "id": 1, "name": "张三" },
{ "id": 2, "name": "李四" }
]
data class User(val id: Int, val name: String)
// Retrofit 接口
@GET("users")
suspend fun listUsers(): List<User>
手动解析时,Moshi 要用 Types.newParameterizedType:
val type = Types.newParameterizedType(List::class.java, User::class.java)
val adapter = moshi.adapter<List<User>>(type)
val users = adapter.fromJson(jsonArrayString)
Gson:
val type = object : TypeToken<List<User>>() {}.type
val users = gson.fromJson(jsonArrayString, type)
可空字段
后端可能不返回某字段或者返回 null,Kotlin 字段写成可空:
data class Article(
val id: Int,
val title: String,
val summary: String? = null, // 可能没返回
val cover: String? = null
)
NoteMoshi 对可空字段处理正确:JSON 没 key 时用默认值(如果有),有 key 但值是 null 时填 null。Gson 不支持默认值,会填 null。
手动解析:JSONObject
有时候 JSON 结构不确定(比如推送的 payload 字段不固定),用 org.json.JSONObject 手动解析:
val jsonStr = """{"name":"张三","age":28,"skills":["kotlin","android"]}"""
try {
val obj = JSONObject(jsonStr)
val name = obj.getString("name")
val age = obj.getInt("age")
val skills = obj.getJSONArray("skills")
for (i in 0 until skills.length()) {
println(skills.getString(i))
}
} catch (e: JSONException) {
e.printStackTrace()
}
API 列表:
getString(key)/getInt(key)/getBoolean(key):取指定类型。getJSONArray(key):取数组。getJSONObject(key):取嵌套对象。optString(key, default):取不到返回默认值,不抛异常。has(key):判断 key 是否存在。
Tip不确定结构用
optXxx系列更安全,不会抛异常。getXxx找不到 key 直接JSONException。
流式解析:JsonReader
JSON 文件特别大(几十 MB)时,全量解析会 OOM。用 JsonReader 流式读:
val reader = JsonReader(reader)
reader.use {
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
"name" -> println(reader.nextString())
"age" -> println(reader.nextInt())
else -> reader.skipValue()
}
}
reader.endObject()
}
skipValue() 跳过不需要的字段,只取关心的,内存占用极小。
配合 Retrofit
实战中 99% 的解析都交给 Retrofit + 转换器,不用手动 fromJson:
val moshi = Moshi.Builder().build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
接口方法返回什么类型,转换器自动解析。你只管写数据类和接口,剩下交给框架。
常见坑
- JSON key 和字段名不一致没映射:解析出来全是 null。用
@Json/@SerializedName。 - 非空字段写成可空:解析没问题,但调用方要处处判空。设计数据类时严格按后端契约。
- Gson 非空字段塞 null:运行时 NPE。改用 Moshi 或者把字段写可空。
List<User>手动解析类型擦除:Moshi 用Types.newParameterizedType,Gson 用TypeToken。- JSON 字段是关键字:比如
class、when,Kotlin 里不能用做字段名,加@Json改名。 - 数字精度:JSON 大整数用
Long别用Int,金额用BigDecimal或者字符串别用Double。
小结
JSON 解析在 Android 就两条路:Moshi(推荐)和 Gson(老项目)。配合 Retrofit 转换器,写数据类 + 接口就完事,框架自动解析。结构不确定时用 JSONObject 手动解析,超大文件用 JsonReader 流式读。字段映射靠 @Json / @SerializedName,可空字段用 ? 标注,默认值用 Moshi 才生效。
到这里网络部分就讲完了,下一章我们进入后台任务与异步,先看 Android 怎么管理后台任务。