kotlinx.serialization:是什么、注解以及 JSON 序列化

作者: IT Sectr 发布日期: 2026-03-15 阅读时间: 12 分钟

kotlinx.serialization — JetBrains 开发的跨平台库,用于将 Kotlin 对象转换为 JSON、ProtoBuf、CBOR 等格式,无需使用反射。与 Gson 和 Moshi 不同,它通过 @Serializable 注解在编译阶段生成序列化器代码,从而提供高性能和类型安全。根据 GitHub Kotlin/kotlinx.serialization,该库支持 Kotlin/JVM、Kotlin/Native、Kotlin/JS 和 Kotlin/Wasm。

要点

  • kotlinx.serialization — 编译时序列化:代码在编译阶段生成,不使用反射
  • @Serializable — 主要注解,启动类的序列化器生成
  • Json {} builder — 通过 Json { ignoreUnknownKeys = true; prettyPrint = true } 配置 JSON
  • 跨平台 — 库在 JVM、Native、JS 和 Wasm 上运行,无需更改 API
  • 自定义序列化器 — 通过 KSerializer 接口用于非标准数据格式

什么是 kotlinx.serialization

kotlinx.serialization — 是 Kotlin 的内置序列化库,由 JetBrains 作为官方 Kotlin 生态系统的一部分开发。它与第三方解决方案(Gson、Moshi、Jackson)的主要区别在于不在运行时使用反射。相反,序列化器代码通过 Kotlin Symbol Processing (KSP) 或 Kotlin 编译器插件在编译阶段生成。与 Gson 相比,性能提升可达 3-5 倍,并保证类型安全。

该库官方支持四种格式:JSON(通过 kotlinx-serialization-json 模块)、ProtoBuf(kotlinx-serialization-protobuf)、CBOR(kotlinx-serialization-cbor)和 HOCON(kotlinx-serialization-hocon)。这些格式作为单独的依赖项添加到 build.gradle.kts 中,从而避免将不必要的库引入项目。每种格式都有自己的配置参数集。

跨平台 — 该库的关键特性。带有 @Serializable 的相同类在所有目标平台上工作:JVM(Android、后端)、Native(iOS)、JS(Web、React)和 Wasm(WebAssembly)。开发人员无需为每个平台编写不同的序列化实现 — 代码保持统一。这在 Kotlin Multiplatform Mobile (KMM) 项目中尤其有价值,其中共享代码在 Android 和 iOS 之间共享。

编译时代码生成如何工作

代码生成 在 kotlinx.serialization 中分三个阶段进行。在第一阶段,Kotlin 编译器检测类上的 @Serializable 注解并将其传递给 Kotlin Symbol Processing (KSP) 插件。在第二阶段,KSP 生成实现 KSerializer 接口的序列化器对象。在第三阶段,生成的代码与项目的源代码一起编译。结果,这些阶段都不会在应用程序运行时执行。

生成的序列化器通过其 getter 和 setter 直接与类的字段一起工作,无需反射。这意味着带有 private 修饰符的字段如果标记了 @Serializable,也会被序列化。这种方法的性能接近于手动序列化:对于简单类(5-10 个字段),序列化时间为 10-50 微秒,对于复杂对象图 — 每 1000 个对象最多 200 微秒。

要在 Android 或 Kotlin/JVM 项目中连接该库,需要在 build.gradle.kts 中添加插件和依赖项。与 Kotlin 版本匹配的 org.jetbrains.kotlin.plugin.serialization 插件会激活代码生成。kotlinx-serialization-json 库在 dependencies 部分中以独立于 Kotlin 版本的版本添加。

kotlin
// build.gradle.kts — 连接 kotlinx.serialization
plugins {
    val kotlinVersion = "2.1.0"
    kotlin("jvm") version kotlinVersion
    kotlin("plugin.serialization") version kotlinVersion
}

dependencies {
    // 主要序列化模块
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")

    // 其他格式
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.3")
}

基本用法:序列化为 JSON

JSON — kotlinx.serialization 中最流行的格式。要序列化一个对象,只需在 data class 上放置 @Serializable 注解并调用 Json.encodeToString()。对于反序列化 — Json.decodeFromString() 并指定类型。该库自动处理 null 字段、列表、嵌套对象和枚举。默认情况下,类的所有字段都是必需的,除非另有说明。

JSON 配置通过 Json {} builder 完成。可以在构造函数中传递 ignoreUnknownKeys = true 以在反序列化时跳过未知字段,prettyPrint = true 用于格式化输出,coerceInputValues = true 用于将错误值转换为默认值。还可以使用 encodeDefaults(序列化具有默认值的字段)和 classDiscriminator(多态序列化的字段名称)设置。

kotlin
// JSON 序列化和反序列化示例
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration

@Serializable
data class Project(
    val name: String,
    val stars: Int,
    val isActive: Boolean = true,
    val languages: List<String> = emptyList()
)

fun main() {
    val project = Project(
        name = "kotlinx.serialization",
        stars = 7200,
        languages = listOf("Kotlin", "Java")
    )

    // 使用 prettyPrint 序列化为 JSON
    val json = Json { prettyPrint = true }
    val jsonString = json.encodeToString(project)
    println(jsonString)
    /*
    {
        "name": "kotlinx.serialization",
        "stars": 7200,
        "isActive": true,
        "languages": ["Kotlin", "Java"]
    }
    */

    // 从 JSON 反序列化
    val decoded = json.decodeFromString<Project>(jsonString)
    println(decoded.name)  // kotlinx.serialization
}

示例展示了序列化和反序列化的 基本循环。带有 @Serializable 注解的 Project data class 自动获得 encodeToString 和 decodeFromString。isActive 字段具有默认值 true — 如果此字段在 JSON 中缺失,则使用默认值。如果没有 ignoreUnknownKeys = true,JSON 中出现未知字段将引发 SerializationException。

密封类的多态序列化

密封类 — kotlinx.serialization 最强大的用例之一。该库支持密封类层次结构的多态序列化,无需额外配置:只需用 @Serializable 标记密封类及其所有子类。序列化时会添加 “type” 字段(可通过 classDiscriminator 配置),反序列化时据此确定具体类型。

kotlin
// 密封类的多态序列化
@Serializable
sealed class Response

@Serializable
data class Success(val data: String) : Response()

@Serializable
data class Error(val code: Int, val message: String) : Response()

fun main() {
    val json = Json { classDiscriminator = "result_type" }

    val responses: List<Response> = listOf(
        Success(data = "Data loaded"),
        Error(code = 404, message = "Not found")
    )

    val jsonString = json.encodeToString(responses)
    println(jsonString)
    /*
    [
        {"result_type":"Success","data":"Data loaded"},
        {"result_type":"Error","code":404,"message":"Not found"}
    ]
    */

    val decoded = json.decodeFromString<List<Response>>(jsonString)
    when (val first = decoded[0]) {
        is Success -> println("Success: ${first.data}")
        is Error -> println("Error: ${first.code}")
    }
}

密封类的多态序列化在 API 客户端 中特别有用,服务器返回不同类型的响应。如果没有 kotlinx.serialization,您需要根据鉴别器字段编写带有 when 的手动反序列化器。使用该库,只需一个注解即可完成。classDiscriminator 允许将标记字段的名称(默认为 “type”)更改为服务器期望的任何值。

kotlinx.serialization 注解:完整概述

该库提供了一套用于微调序列化的注解。主要的是用于类的 @Serializable。其他注解:@SerialName 用于指定 JSON 中的字段名称(如果与 Kotlin 名称不同),@Transient 用于从序列化中排除字段,@Required 用于必须在 JSON 中存在的字段,@EncodeDefault 用于强制序列化具有默认值的字段。

注解用途示例
@Serializable为类启用序列化器生成@Serializable data class User
@SerialName指定字段在格式中的替代名称@SerialName(“user_name”) val name: String
@Transient从序列化中排除字段@Transient val cache: MutableMap
@Required反序列化时字段在 JSON 中是必需的@Required val id: String
@EncodeDefault即使使用默认值也序列化字段@EncodeDefault val type: Type = Type.A
@Serializer将自定义序列化器连接到类@Serializer(forClass = Date::class)

@SerialName 注解在使用字段名称为 snake_case 而 Kotlin 风格为 camelCase 的 API 时至关重要。例如,服务器发送 “user_id”,而在 Kotlin 代码中使用 userId。@SerialName(“user_id”) 无需额外的映射器即可解决此问题。@Transient 适用于不需要发送到服务器的字段 — 例如,临时计算值或缓存。

@Required 作为可空字段的替代方案

默认情况下,kotlinx.serialization 中的所有字段都是必需的。如果字段可能在 JSON 中缺失,则必须使其可为空 (String?) 或设置默认值 (val name: String = “”)。但是,有时字段在 Kotlin 中不可为空,但由于 API 版本管理可能在 JSON 中缺失。在这种情况下,@Required 在字段缺失时抛出 SerializationException,而默认值则无错误地填充默认值。

自定义序列化器:KSerializer 和手动管理

KSerializer — kotlinx.serialization 中所有序列化器实现的接口。如果标准代码生成不合适(例如,用于处理 Date、Bitmap 或特定的二进制格式),您可以编写自己的序列化器。为此,您需要实现 serialize() 和 deserialize() 方法,并提供描述符 — 格式模式的结构描述。

自定义序列化器有两种连接方式:通过 @Serializable(with = MySerializer::class) 注解连接到特定类,或通过 Json { serializersModule = ... } 全局连接到类型的所有实例。第二种方式更适合内置类型(Date、UUID),以避免在每个字段上编写注解。

kotlin
// java.util.Date 的自定义序列化器
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

object DateSerializer : KSerializer<Date> {
    private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)

    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: Date) {
        encoder.encodeString(dateFormat.format(value))
    }

    override fun deserialize(decoder: Decoder): Date {
        return dateFormat.parse(decoder.decodeString())
    }
}

// 使用自定义序列化器
@Serializable
data class Event(
    val title: String,
    @Serializable(with = DateSerializer::class)
    val date: Date
)

fun main() {
    val json = Json { prettyPrint = true }
    val event = Event("发布", Date())
    println(json.encodeToString(event))
}

在示例中,DateSerializer 将 java.util.Date 转换为 ISO 8601 字符串。如果没有自定义序列化器,kotlinx.serialization 无法处理 Date — 这是一种不属于 Kotlin 标准库的类型。特定字段上的 @Serializable(with = DateSerializer::class) 仅对该字段连接序列化器。要全局注册所有 Date,请使用 Json { serializersModule = SerializersModule { contextual(DateSerializer) } }。

序列化格式:JSON、ProtoBuf、CBOR、HOCON

kotlinx.serialization 不仅限于 JSON。该库支持四种内置格式,每种都有自己的模块和配置。JSON (kotlinx-serialization-json) — 通用,人类可读,适用于 REST API。ProtoBuf (kotlinx-serialization-protobuf) — 二进制,紧凑,带必需的模式,适用于高负载微服务。CBOR (kotlinx-serialization-cbor) — JSON 的二进制对应物,适用于 IoT 和流量受限的移动设备。HOCON (kotlinx-serialization-hocon) — 配置格式,兼容 TypeSafe Config。

格式模块类型模式典型用途
JSONkotlinx-serialization-json文本可选REST API、数据存储
ProtoBufkotlinx-serialization-protobuf二进制必需 (.proto)微服务、gRPC
CBORkotlinx-serialization-cbor二进制可选IoT、移动设备
HOCONkotlinx-serialization-hocon文本可选配置文件

ProtoBuf 需要在 .proto 文件中定义模式,但 kotlinx-serialization-protobuf 直接从 @Serializable 生成 Kotlin 类,无需 .proto。这简化了开发:只需注释 data class 并使用 ProtoBuf.encodeToByteArray()。CBOR 对于 Android 框架特别有用,当需要通过 NFC 或 BLE 传输紧凑的二进制数据时。CBOR 消息的大小比相同数据集的 JSON 平均小 20-30%。

为项目选择格式

对于移动应用中的 REST API,JSON 是最佳的 — 无需额外工具即可调试,在日志中可读,并与任何后端兼容。如果应用在微服务之间传输大量数据(数百兆字节)— ProtoBuf 凭借二进制编码可提供高达 5 倍的速度提升。对于在文件中存储设置,请使用 HOCON 或 JSON。对于流量限制严格的设备(IoT 传感器)— CBOR。

使用 kotlinx.serialization 时的常见错误

第一个错误 — 反序列化时忽略未知键。如果服务器添加了新字段并且您设置了 ignoreUnknownKeys = false,应用程序将因 SerializationException 而崩溃。默认情况下,此标志是关闭的。解决方案:始终为生产代码设置 Json { ignoreUnknownKeys = true },以便对 API 变更具有弹性。

第二个错误 — 在 data class 中序列化 internal 或 private 字段。在 Kotlin data class 中,主构造函数中的所有字段默认都会被序列化。如果字段包含敏感数据(密码、令牌),则必须使用 @Transient 标记或将其从主构造函数中移出。@Transient 将字段完全排除在 JSON 之外,但在构造函数中可能会导致错误 — 最好在类体中用 @Transient 定义此类字段。

第三个错误 — 没有密封类的多态序列化。如果您使用 open class 而不是密封类,kotlinx.serialization 需要在 serializersModule 中显式注册所有子类。与密封类不同,编译器知道所有子类,而 open class 允许任意扩展 — 库无法自动确定所有子类型。注册通过 Json { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } } 完成。

库版本管理错误

kotlinx.serialization 的版本必须与 Kotlin 版本兼容。JetBrains 发布了兼容性表:kotlinx-serialization 1.6.x 与 Kotlin 1.9.x 兼容,1.7.x — 与 Kotlin 2.0.x 和 2.1.x 兼容。版本不匹配会导致神秘的编译错误,如 “Symbol ‘serializer’ is missing”。始终在 mavenCentral 或项目的 GitHub 仓库中检查当前版本。

常见问题

kotlinx.serialization 与 Gson 和 Moshi 有何不同?

kotlinx.serialization 通过 KSP 使用编译时代码生成,而 Gson 和 Moshi 使用运行时反射。这提供了性能优势(比 Gson 快 3-5 倍)和类型安全。Gson 会序列化任何没有注解的字段,这可能导致数据泄露。kotlinx.serialization 需要显式的 @Serializable 注解,这更安全。Moshi 也支持 codegen,但仅适用于 JVM 和 Android。

kotlinx.serialization 支持 Kotlin Multiplatform 吗?

是的,kotlinx.serialization 是 JetBrains 的官方跨平台库。它支持 Kotlin/JVM(Android、后端)、Kotlin/Native(iOS)、Kotlin/JS(Web、React)和 Kotlin/Wasm。API 对所有平台都是统一的:@Serializable + Json.encodeToString() 在任何地方都以相同方式工作。iOS 不需要额外的设置 — Kotlin/Native 将序列化代码编译为本机二进制文件。

如何处理 JSON 中的 null 字段?

可空字段 (String?) 如果 JSON 中的值缺失或指定为 null,则反序列化为 null。对于没有默认值的非可空字段 (String),JSON 中字段的缺失将导致 SerializationException。如果您希望 null 值不进入 JSON,请配置 Json { encodeDefaults = false }。这将从输出中排除所有等于默认值的字段(包括可空字段的 null)。

如果服务器发送 snake_case 字段怎么办?

在每个名称与 Kotlin 格式不同的字段上使用 @SerialName(“snake_case_name”)。另外,对于 Kotlin 2.0+,可以使用 Json { namingStrategy = JsonNamingStrategy.SnakeCase } — 自动 camelCase ↔ snake_case 转换。此设置同时应用于所有字段。如果需要部分定制,请将 @SerialName 与全局策略结合使用。

可以序列化 Kotlin Flow 或 coroutine 吗?

不可以,Flow 和协程不能直接序列化 — 它们代表异步执行,而不是数据。要传输 Flow 中的数据,您需要通过 .toList() 在协程中将其收集到集合中,然后序列化该集合。同样,Job、Deferred 或 Continuation 也无法序列化。只序列化 data class — 没有行为逻辑的数据模型。

总结

  • kotlinx.serialization — 通过 @Serializable 进行编译时序列化,无反射,性能比 Gson 高 5 倍
  • @Serializable、@SerialName、@Transient — 配置字段和类序列化的关键注解
  • Json {} builder 配置 JSON:ignoreUnknownKeys、prettyPrint、coerceInputValues、encodeDefaults
  • 密封类 和多态序列化 — 无需额外代码即可无缝支持类型层次结构
  • KSerializer — 用于非标准类型(Date、Bitmap、UUID)的自定义序列化器接口
  • 四种格式:JSON、ProtoBuf、CBOR、HOCON — 通过模块连接,API 统一
  • 跨平台 — JVM、Native、JS 和 Wasm 的统一代码;对 KMM 和共享模块至关重要

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读