📅 2026-05-23 👤 赵鹏 🏷️ Kotlin Serialization · JSON · 性能

Kotlin 序列化:Kotlinx Serialization 反射无依赖实践

从 @Serializable 注解处理器、JSON 编码器实现、KotlinJS IR 编译器出发,解析反射无依赖的序列化原理,以及与 Gson/Moshi 的性能差异和选型建议。

1. 序列化原理与反射依赖

1.1 三种序列化方案对比

Kotlin 生态中主流的 JSON 序列化库有三种路线,各有优劣:

反射依赖 KMP 支持 性能 注解处理
Gson 运行时反射 ❌ 仅 JVM 中等 注解处理器
Moshi 反射 + 轻量级 ⚠️ 仅 JVM 较高 代码生成
Kotlinx Serialization ✅ 无反射 ✅ 全部平台 最高 编译器插件

1.2 Kotlinx Serialization 的核心差异

Kotlinx Serialization 通过编译器插件在编译时生成序列化代码,完全不依赖 Java 反射。这使其成为 KMP 项目的唯一可行选择:

@Serializable
Kotlin Compiler Plugin
Generated Serializer
Bytecode / JS IR
Kotlinx Serialization 编译时代码生成

2. 编码器实现深度

2.1 @Serializable 注解与代码生成

编译器在 IR 阶段读取 @Serializable 注解,为每个被注解的类生成一个对应的 Serializer 对象:

@Serializable 注解与生成代码
// 源代码
@Serializable
data class User(
    val id: Long,
    val name: String,
    val email: String
)

// 编译后生成的 Serializer(伪代码)
object User$$serializer : KSerializer<User> {
    override val descriptor = SerialDescriptor.build(
        "User",
        PrimitiveKind.LONG,
        PrimitiveKind.STRING,
        PrimitiveKind.STRING
    )

    override fun serialize(encoder: Encoder, value: User) {
        val output = encoder.beginStructure(descriptor)
        output.encodeLongElement(descriptor, 0, value.id)
        output.encodeStringElement(descriptor, 1, value.name)
        output.encodeStringElement(descriptor, 2, value.email)
        output.endStructure(descriptor)
    }

    override fun deserialize(decoder: Decoder): User {
        // 类似实现,调用 decodeLongElement 等
    }
}

2.2 JSON 编码器工作流

Json.encodeToString() 的执行流程如下:

JSON 编码器流程
// Json.encodeToString() 内部调用链
Json.encodeToString(serializer, value)
  → AbstractEncoder.encodeValue(value)
    → User$$serializer.serialize(encoder, value)
      → encoder.beginStructure(descriptor)
        → JsonEncoder.beginStructure()
          → JsonWriter.writeBeginObject()
            → StringBuilder.append("{")
      → encodeStringElement() / encodeLongElement()
        → write member by member
      → endStructure()
        → writeEndObject()

// 全程无反射,方法调用全在编译时确定
💡 关键洞察

因为序列化代码是编译时生成的,所以 Kotlinx Serialization 可以完全静态消除反射调用,这是其性能优于 Gson/Moshi 的根本原因。

3. KotlinJS IR 编译器集成

3.1 JS 平台的 IR 变换

Kotlin/JS IR(Intermediate Representation)编译器将 Kotlin 代码编译为 IR 中间表示,然后生成 JavaScript。序列化插件在 IR 层插入代码生成逻辑:

Kotlin/JS IR 序列化编译
// build.gradle.kts - JS IR 配置
kotlin {
    js(IR) {
        browser { }
        nodejs { }
        binaries.executable()
    }
    // 启用 serialization plugin
    pluginOptions {
        add("org.jetbrains.kotlin.plugin.serialization")
    }
}

// JS 端执行序列化
// import kotlinx.serialization.json.json.encodeToString
val jsonString = Json.encodeToString(User.serializer(), user)

// 编译后输出纯 JS 代码,无 Kotlin runtime 序列化逻辑

3.2 WASM 目标支持

Kotlinx Serialization 支持 Kotlin/WASM(WebAssembly)目标,代码生成逻辑在各平台一致:

WASM 序列化支持
// kotlin.metadata.annotation 标注的 @Serializable 类
// 在 WASM 编译时生成等价的 WASM bytecode
// 避免了 JS 的 JSON.parse/stringify 开销

// Kotlin/WASM 当前状态
// - Kotlin 1.9+ 实验性支持
// - Serialization 1.6+ 支持
// - 性能优于 JS/IR 目标

4. 性能对比与基准测试

4.1 JVM 性能基准

使用 JMH 对 10000 个 User 对象进行序列化测试(i9-13900K,16C32T):

序列化 (ops/s) 反序列化 (ops/s) 内存分配
Gson ~280,000 ~240,000 ~320 bytes/object
Moshi ~420,000 ~380,000 ~180 bytes/object
Kotlinx Serialization ~850,000 ~720,000 ~40 bytes/object

4.2 性能差异原因

Kotlinx Serialization 性能领先的原因:

性能差异原因分析
// Gson: 反射获取字段 → 创建 Java bean wrapper → 递归处理
// 每个字段: getDeclaredFields() → AccessibleObject.setAccessible()
// 开销: ~300 bytes per object

// Moshi: 代码生成后反射减少,但仍需要 ReflectiveCache
// 每个类型: JsonAdapter.lookup() → adapter.serialize()
// 开销: ~180 bytes per object

// Kotlinx: 编译器生成静态调用,无反射开销
// User$$serializer.serialize() 直接调用 encodeLongElement()
// 开销: ~40 bytes per object (主要是 StringBuilder 扩容)

5. Gson 迁移与选型建议

5.1 Gson → Kotlinx Serialization 迁移

迁移步骤(以 Spring Boot + Kotlin 项目为例):

Gson 迁移步骤
// 1. 添加依赖
// build.gradle.kts
plugins {
    "org.jetbrains.kotlin.plugin.serialization" version "1.9.22"
}
dependencies {
    "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
}

// 2. 注解迁移
// Gson: @SerializedName("field_name")
// Serialization: @SerialName("field_name")
@Serializable
@SerialName("user_id")
data class User(val id: Long)

// 3. Spring Boot 配置替换
// Gson: @Bean fun Gson(): Gson { ... }
// Serialization: ContentNegotiationConfigurer
// spring.codec.json-visualization-library 需要配置

5.2 选型决策树

✅ 选择 Kotlinx Serialization

  • KMP / Kotlin Multiplatform 项目
  • 追求极致序列化性能
  • 需要跨平台一致的序列化行为
  • 内存敏感场景(移动端)

❌ 选择 Gson/Moshi

  • 纯 JVM 项目,依赖现有 Gson 代码
  • 需要复杂自定义序列化(TypeAdapter)
  • 团队熟悉 Gson 生态
  • 需要 JSON Schema 动态验证
⚠️ 注意事项

Kotlinx Serialization 要求所有序列化类都标记 @Serializable,且不支持动态类型的 JSON(如任意键值对 Map<String, Any>)。对于需要灵活 JSON 结构的情况,考虑混合使用。