首页 / Android 入门教程 / JSON 解析

Android 入门教程

JSON 解析

本教程共 100 篇 · 第 72 篇 · 更新于 2026-07-28 · 约 8 分钟阅读

AndroidAndroid 入门教程JSONMoshiGsonRetrofit数据解析

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)

选哪个

维度GsonMoshi
Kotlin 可空类型容易踩坑,null 能塞进非空字段严格校验,违反就抛异常
默认值不支持支持
性能反射,慢KSP 生成代码,快
生态老项目都在用新项目首选
学习成本

我的建议:新项目用 Moshi + KSP,老项目继续 Gson。本教程主线用 Moshi。

Warning

Gson 在 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
)
Note

Moshi 对可空字段处理正确: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()

接口方法返回什么类型,转换器自动解析。你只管写数据类和接口,剩下交给框架。

常见坑

  1. JSON key 和字段名不一致没映射:解析出来全是 null。用 @Json / @SerializedName
  2. 非空字段写成可空:解析没问题,但调用方要处处判空。设计数据类时严格按后端契约。
  3. Gson 非空字段塞 null:运行时 NPE。改用 Moshi 或者把字段写可空。
  4. List<User> 手动解析类型擦除:Moshi 用 Types.newParameterizedType,Gson 用 TypeToken
  5. JSON 字段是关键字:比如 classwhen,Kotlin 里不能用做字段名,加 @Json 改名。
  6. 数字精度:JSON 大整数用 Long 别用 Int,金额用 BigDecimal 或者字符串别用 Double

小结

JSON 解析在 Android 就两条路:Moshi(推荐)和 Gson(老项目)。配合 Retrofit 转换器,写数据类 + 接口就完事,框架自动解析。结构不确定时用 JSONObject 手动解析,超大文件用 JsonReader 流式读。字段映射靠 @Json / @SerializedName,可空字段用 ? 标注,默认值用 Moshi 才生效。

到这里网络部分就讲完了,下一章我们进入后台任务与异步,先看 Android 怎么管理后台任务。