kotlinx.serialization — JetBrains 开发的跨平台库,用于将 Kotlin 对象转换为 JSON、ProtoBuf、CBOR 等格式,无需使用反射。与 Gson 和 Moshi 不同,它通过 @Serializable 注解在编译阶段生成序列化器代码,从而提供高性能和类型安全。根据 GitHub Kotlin/kotlinx.serialization,该库支持 Kotlin/JVM、Kotlin/Native、Kotlin/JS 和 Kotlin/Wasm。
要点
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 版本的版本添加。
// 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 — kotlinx.serialization 中最流行的格式。要序列化一个对象,只需在 data class 上放置 @Serializable 注解并调用 Json.encodeToString()。对于反序列化 — Json.decodeFromString() 并指定类型。该库自动处理 null 字段、列表、嵌套对象和枚举。默认情况下,类的所有字段都是必需的,除非另有说明。
JSON 配置通过 Json {} builder 完成。可以在构造函数中传递 ignoreUnknownKeys = true 以在反序列化时跳过未知字段,prettyPrint = true 用于格式化输出,coerceInputValues = true 用于将错误值转换为默认值。还可以使用 encodeDefaults(序列化具有默认值的字段)和 classDiscriminator(多态序列化的字段名称)设置。
// 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 配置),反序列化时据此确定具体类型。
// 密封类的多态序列化
@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”)更改为服务器期望的任何值。
该库提供了一套用于微调序列化的注解。主要的是用于类的 @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 适用于不需要发送到服务器的字段 — 例如,临时计算值或缓存。
默认情况下,kotlinx.serialization 中的所有字段都是必需的。如果字段可能在 JSON 中缺失,则必须使其可为空 (String?) 或设置默认值 (val name: String = “”)。但是,有时字段在 Kotlin 中不可为空,但由于 API 版本管理可能在 JSON 中缺失。在这种情况下,@Required 在字段缺失时抛出 SerializationException,而默认值则无错误地填充默认值。
KSerializer — kotlinx.serialization 中所有序列化器实现的接口。如果标准代码生成不合适(例如,用于处理 Date、Bitmap 或特定的二进制格式),您可以编写自己的序列化器。为此,您需要实现 serialize() 和 deserialize() 方法,并提供描述符 — 格式模式的结构描述。
自定义序列化器有两种连接方式:通过 @Serializable(with = MySerializer::class) 注解连接到特定类,或通过 Json { serializersModule = ... } 全局连接到类型的所有实例。第二种方式更适合内置类型(Date、UUID),以避免在每个字段上编写注解。
// 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) } }。
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。
| 格式 | 模块 | 类型 | 模式 | 典型用途 |
|---|---|---|---|---|
| JSON | kotlinx-serialization-json | 文本 | 可选 | REST API、数据存储 |
| ProtoBuf | kotlinx-serialization-protobuf | 二进制 | 必需 (.proto) | 微服务、gRPC |
| CBOR | kotlinx-serialization-cbor | 二进制 | 可选 | IoT、移动设备 |
| HOCON | kotlinx-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。
第一个错误 — 反序列化时忽略未知键。如果服务器添加了新字段并且您设置了 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 通过 KSP 使用编译时代码生成,而 Gson 和 Moshi 使用运行时反射。这提供了性能优势(比 Gson 快 3-5 倍)和类型安全。Gson 会序列化任何没有注解的字段,这可能导致数据泄露。kotlinx.serialization 需要显式的 @Serializable 注解,这更安全。Moshi 也支持 codegen,但仅适用于 JVM 和 Android。
是的,kotlinx.serialization 是 JetBrains 的官方跨平台库。它支持 Kotlin/JVM(Android、后端)、Kotlin/Native(iOS)、Kotlin/JS(Web、React)和 Kotlin/Wasm。API 对所有平台都是统一的:@Serializable + Json.encodeToString() 在任何地方都以相同方式工作。iOS 不需要额外的设置 — Kotlin/Native 将序列化代码编译为本机二进制文件。
可空字段 (String?) 如果 JSON 中的值缺失或指定为 null,则反序列化为 null。对于没有默认值的非可空字段 (String),JSON 中字段的缺失将导致 SerializationException。如果您希望 null 值不进入 JSON,请配置 Json { encodeDefaults = false }。这将从输出中排除所有等于默认值的字段(包括可空字段的 null)。
在每个名称与 Kotlin 格式不同的字段上使用 @SerialName(“snake_case_name”)。另外,对于 Kotlin 2.0+,可以使用 Json { namingStrategy = JsonNamingStrategy.SnakeCase } — 自动 camelCase ↔ snake_case 转换。此设置同时应用于所有字段。如果需要部分定制,请将 @SerialName 与全局策略结合使用。
不可以,Flow 和协程不能直接序列化 — 它们代表异步执行,而不是数据。要传输 Flow 中的数据,您需要通过 .toList() 在协程中将其收集到集合中,然后序列化该集合。同样,Job、Deferred 或 Continuation 也无法序列化。只序列化 data class — 没有行为逻辑的数据模型。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。