Kotlin Serialization (JSON 處理)

在 Kotlin 開發中,處理 JSON 是最常見的任務之一。雖然過去我們常使用 Gson 或 Moshi,但現在官方推薦的解決方案是 kotlinx.serialization

為什麼選擇 kotlinx.serialization?

  1. Kotlin First:由 JetBrains 官方開發,完美支援 Kotlin 特性(如 Null 安全、預設值)。
  2. Multiplatform:支援 JVM, JS, Native, iOS,適合 KMP 專案。
  3. 零反射 (No Reflection):編譯時生成 Serializer,效能極佳且不需要 ProGuard 設定。

安裝設定

kotlinx.serialization 包含兩個部分:Gradle PluginRuntime Library

Build Script (build.gradle.kts):

plugins {
    kotlin("jvm") version "1.9.0" // 或 android
    // 加入 Serialization Plugin
    kotlin("plugin.serialization") version "1.9.0"
}

dependencies {
    // 加入 JSON 處理函式庫
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}

標記類別 (@Serializable)

只需在 Data Class 上加上 @Serializable,編譯器就會自動產生序列化所需的程式碼。

import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@Serializable
data class User(val name: String, val age: Int)

fun main() {
    val user = User("Mike", 30)

    // 序列化 (Object -> JSON)
    val jsonString = Json.encodeToString(user)
    println(jsonString) 
    // 輸出: {"name":"Mike","age":30}

    // 反序列化 (JSON -> Object)
    val obj = Json.decodeFromString<User>(jsonString)
    println(obj)
    // 輸出: User(name=Mike, age=30)
}

JSON 設定 (Json Configuration)

預設的 Json 物件非常嚴格。在實務上(特別是串接 API),我們通常需要調整設定來避免崩潰。

建議建立一個全域的 Json 實體:

val json = Json {
    // 忽略未知的 Key (非常重要!避免 API 加欄位導致 App 崩潰)
    ignoreUnknownKeys = true
    
    // 寬容模式 (允許 Key 不帶引號等非標準 JSON)
    isLenient = true
    
    // 輸出格式化 (Pretty Print)
    prettyPrint = true
    
    // 如果欄位是預設值,是否需要寫入 JSON (預設為 false 以節省頻寬)
    encodeDefaults = true
}

使用方式:

val data = json.decodeFromString<User>(input)

常用 Annotations

自定義欄位名稱 (@SerialName)

當 JSON 的 key (user_name) 與 Kotlin 的變數名 (userName) 不同時使用。這也同時支援多型 (Polymorphism) 的 discriminator 值。

@Serializable
data class User(
    @SerialName("user_name") 
    val userName: String
)

忽略欄位 (@Transient)

如果某個欄位不需要參與序列化,可以使用 @Transient。注意:該欄位必須要有預設值。

@Serializable
data class User(
    val name: String,
    @Transient 
    val isLogin: Boolean = false // 不會出現在 JSON 中
)

預設值與可選欄位

如果 JSON 中可能缺少某個欄位,只需在 Kotlin 中提供預設值即可。

@Serializable
data class Response(
    val id: Int,
    val message: String = "Success" // 如果 JSON 沒給 message,就用這個值
)

泛型支援 (Generic Classes)

kotlinx.serialization 對泛型的支援非常優秀。

@Serializable
data class ApiResponse<T>(
    val status: Int,
    val data: T
)

fun main() {
    val jsonString = """{"status": 200, "data": {"name": "Mike", "age": 30}}"""
    
    // 直接指定泛型型別即可
    val response = Json.decodeFromString<ApiResponse<User>>(jsonString)
    
    println(response.data.name) // Mike
}

與 Gson / Moshi 的比較

特性kotlinx.serializationGsonMoshi
原理編譯時生成 (Compiler Plugin)執行時反射 (Reflection)兩者皆可
Kotlin 支援完美 (Null 安全、預設值)普通 (需額外處理 Null)優秀
效能⭐⭐⭐⭐⭐⭐⭐⭐⭐
多平台 (KMP)✅ 支援❌ 僅 JVM❌ 僅 JVM

結論:如果是全新的 Kotlin 專案,請無腦選擇 kotlinx.serialization