Kotlin 数据序列化:使用 kotlinx.serialization 库深度解析
在现代软件开发中,数据的传输和存储无处不在。无论是网络请求、本地存储、进程间通信还是配置文件,我们都需要将内存中的数据结构(对象)转换为某种格式进行传输或存储,然后再将其恢复成内存中的数据结构。这个过程就是 序列化(Serialization) 和 反序列化(Deserialization)。
对于使用 Kotlin 开发的应用程序而言,选择一个高效、灵活且与语言特性紧密集成的序列化库至关重要。kotlinx.serialization
就是 JetBrains 官方提供的解决方案,它是一个多平台序列化库,支持多种格式,并且与 Kotlin 语言的特性(如数据类、可空性、密封类等)无缝集成。
本文将深入探讨 kotlinx.serialization
库的使用,从基础入门到高级特性,帮助你全面掌握这个强大的工具。
1. 为什么选择 kotlinx.serialization
?
在 Kotlin 生态系统中,存在许多成熟的序列化库,比如 Jackson、Gson 等,它们主要来源于 Java 生态。然而,kotlinx.serialization
具有一些独特的优势:
- Kotlin 原生支持: 它是一个 Kotlin-first 的库,设计时就考虑了 Kotlin 的语言特性。例如,它能很好地处理 Kotlin 的可空类型、默认参数以及数据类。
- 多平台:
kotlinx.serialization
支持 Kotlin/JVM, Kotlin/JS, Kotlin/Native 等多个平台,这对于开发多平台应用(KMM – Kotlin Multiplatform Mobile)非常有益,可以共享序列化逻辑。 - 编译器插件:
kotlinx.serialization
的核心是一个 Kotlin 编译器插件。这个插件会在编译时为标记了@Serializable
注解的类自动生成序列化和反序列化的代码。这带来了以下好处:- 性能: 避免了运行时反射,提高了序列化和反序列化的速度。
- 类型安全: 编译时生成代码,可以提前发现潜在的序列化问题。
- 零样板代码: 对于简单的数据类,只需要一个注解即可实现序列化,无需手动编写序列化逻辑。
- 多种格式支持: 库本身是格式无关的,通过不同的模块支持多种数据格式,如 JSON (最常用)、Protobuf、CBOR 等。
- 高度可配置和扩展: 提供了丰富的 API 来自定义序列化行为,处理复杂类型、遗留格式或实现特定的序列化逻辑。
总而言之,如果你在 Kotlin 项目中需要处理数据序列化,kotlinx.serialization
是一个官方推荐、性能优异、类型安全且与 Kotlin 语言高度契合的首选方案。
2. 入门:添加依赖和插件
使用 kotlinx.serialization
需要在项目中添加相应的依赖和应用 Kotlin 编译器插件。这通常在你的构建脚本中完成(例如,使用 Gradle)。
以 build.gradle.kts
为例:
“`kotlin
plugins {
kotlin(“jvm”) // 或 kotlin(“multiplatform”), kotlin(“android”), kotlin(“js”), kotlin(“native”)
kotlin(“plugin.serialization”) version “1.9.23” // 使用你的 Kotlin 版本对应的序列化插件版本
}
dependencies {
// 根据你需要支持的格式添加对应的 runtime 依赖
// 例如,支持 JSON 格式
implementation(“org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3”) // 使用最新的 serialization runtime 版本
// 其他格式如 CBOR, Protobuf:
// implementation(“org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.6.3”)
// implementation(“org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.6.3”)
// 其他项目依赖...
}
“`
在 build.gradle
(Groovy) 中:
“`groovy
plugins {
id ‘org.jetbrains.kotlin.jvm’ // 或其他平台插件
id ‘org.jetbrains.kotlin.plugin.serialization’ version ‘1.9.23’
}
dependencies {
implementation ‘org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3’
// 其他格式依赖…
}
“`
请确保你使用的 Kotlin 版本、序列化插件版本和运行时库版本相互兼容。通常,序列化插件版本应与你的 Kotlin 版本相同,而运行时库版本可以独立更新到最新稳定版。
应用 kotlin("plugin.serialization")
插件是关键一步,它使得编译器能够识别并处理 @Serializable
注解。
3. 基础用法:序列化和反序列化数据类
最常见的场景是序列化和反序列化 Kotlin 的数据类。
首先,你需要导入 kotlinx.serialization
相关的包:
kotlin
import kotlinx.serialization.*
import kotlinx.serialization.json.*
然后,为你的数据类添加 @Serializable
注解:
kotlin
@Serializable
data class User(val name: String, val age: Int)
有了这个注解,编译器插件就会为 User
类生成必要的序列化器代码。
接下来,你可以使用 Json
对象(或其他格式的入口点)进行序列化和反序列化:
“`kotlin
fun main() {
// 创建一个 User 对象
val user = User(“Alice”, 30)
// 序列化为 JSON 字符串
val jsonString = Json.encodeToString(user)
println("Serialized JSON: $jsonString") // Output: {"name":"Alice","age":30}
// 反序列化回 User 对象
val decodedUser = Json.decodeFromString<User>(jsonString)
println("Deserialized User: $decodedUser") // Output: User(name=Alice, age=30)
// 验证反序列化结果
println("Decoded user matches original: ${user == decodedUser}") // Output: Decoded user matches original: true
}
“`
在这个简单的例子中:
Json.encodeToString(value)
函数将一个@Serializable
对象序列化为 JSON 字符串。Json.decodeFromString<Type>(string)
函数将 JSON 字符串反序列化回指定类型的对象。请注意,由于 Kotlin 的类型擦除,通常需要通过<Type>
显式指定目标类型。
这就是 kotlinx.serialization
的基本工作方式:标记类为 @Serializable
-> 使用格式特定的编码器/解码器对象进行操作。
4. 支持的格式
kotlinx.serialization
库本身是格式无关的,通过引入不同的运行时模块来支持不同的数据格式。最常用的是 kotlinx-serialization-json
。其他官方支持或社区贡献的格式包括:
- JSON:
kotlinx-serialization-json
。最流行的数据交换格式,支持度最好。 - Protobuf (Protocol Buffers):
kotlinx-serialization-protobuf
。Google 开发的高效、紧凑的二进制格式,常用于网络传输和存储。 - CBOR (Concise Binary Object Representation):
kotlinx-serialization-cbor
。一种紧凑的二进制数据格式,设计上类似于 JSON,但更适合机器处理。 - Properties:
kotlinx-serialization-properties
。将对象序列化为属性文件格式。 - Hocon: 社区贡献的模块,支持 HOCON (Human-Optimized Config Object Notation) 格式。
- XML: 目前官方没有提供成熟的 XML 模块,但社区可能有相关的实现。通常在 Kotlin 中处理 XML 会考虑其他专门的 XML 库。
本文将主要以 JSON 格式 为例进行讲解,因为它最常用且能展示 kotlinx.serialization
的绝大多数特性。
5. 高级特性和自定义
基础用法对于简单的数据类已经足够,但在实际应用中,你可能会遇到更复杂的场景,需要更精细地控制序列化过程。kotlinx.serialization
提供了丰富的注解和 API 来处理这些情况。
5.1 处理可空类型和默认值
Kotlin 的可空性和默认参数是其重要特性。kotlinx.serialization
能很好地处理它们。
-
可空类型: 如果一个属性是可空的 (
String?
),序列化时如果值为null
,默认会被省略(不出现在 JSON 中)。反序列化时,如果 JSON 中缺少对应的 key,可空属性会被赋值为null
。“`kotlin
@Serializable
data class Product(val name: String, val description: String? = null, val price: Double)fun main() {
val product1 = Product(“Book”, price = 20.0) // description 为 null
val product2 = Product(“Laptop”, “Powerful machine”, 1200.0)println(Json.encodeToString(product1)) // Output: {"name":"Book","price":20.0} - description 被省略 println(Json.encodeToString(product2)) // Output: {"name":"Laptop","description":"Powerful machine","price":1200.0} val jsonString1 = """{"name":"Pen","price":1.5}""" val decodedProduct1 = Json.decodeFromString<Product>(jsonString1) println(decodedProduct1) // Output: Product(name=Pen, description=null, price=1.5) - description 自动设为 null val jsonString2 = """{"name":"Eraser","description":"Soft","price":0.8}""" val decodedProduct2 = Json.decodeFromString<Product>(jsonString2) println(decodedProduct2) // Output: Product(name=Eraser, description=Soft, price=0.8)
}
“` -
默认值: 如果一个属性有默认值 (
String = "default"
),序列化时如果属性值等于其默认值,默认会被省略。反序列化时,如果 JSON 中缺少对应的 key,属性会被赋值为其默认值。“`kotlin
@Serializable
data class Settings(val theme: String = “light”, val notificationsEnabled: Boolean = true)fun main() {
val settings1 = Settings() // 所有属性都是默认值
val settings2 = Settings(theme = “dark”) // notificationsEnabled 是默认值println(Json.encodeToString(settings1)) // Output: {} - 所有默认值都被省略 println(Json.encodeToString(settings2)) // Output: {"theme":"dark"} - notificationsEnabled 被省略 val jsonString1 = """{}""" val decodedSettings1 = Json.decodeFromString<Settings>(jsonString1) println(decodedSettings1) // Output: Settings(theme=light, notificationsEnabled=true) - 缺失的属性使用默认值 val jsonString2 = """{"theme":"dark","notificationsEnabled":false}""" val decodedSettings2 = Json.decodeFromString<Settings>(jsonString2) println(decodedSettings2) // Output: Settings(theme=dark, notificationsEnabled=false)
}
“` -
强制编码默认值 (
@EncodeDefault
): 如果你希望即使属性值是默认值,也在 JSON 中包含这个 key-value 对,可以使用@EncodeDefault
注解。“`kotlin
@Serializable
data class Config(@EncodeDefault val retryCount: Int = 3, val timeout: Long)fun main() {
val config = Config(timeout = 1000L) // retryCount 是默认值println(Json.encodeToString(config)) // Output: {"retryCount":3,"timeout":1000} - retryCount 没有被省略
}
“`@EncodeDefault
可以应用于属性,也可以应用于整个类(强制编码所有有默认值的属性)。
5.2 自定义 JSON 字段名 (@SerialName
)
有时候,你的数据类属性名与 JSON 字段名不匹配(例如,为了遵循 Kotlin 的命名规范或与旧的 API 对齐)。可以使用 @SerialName
注解来指定 JSON 中的字段名。
“`kotlin
@Serializable
data class UserProfile(
@SerialName(“user_id”) val userId: String,
val name: String,
@SerialName(“email_address”) val email: String?
)
fun main() {
val profile = UserProfile(“12345”, “Bob”, “[email protected]”)
val jsonString = Json.encodeToString(profile)
println(jsonString) // Output: {"user_id":"12345","name":"Bob","email_address":"[email protected]"}
val jsonInput = """{"user_id":"67890","name":"Charlie","email_address":null}"""
val decodedProfile = Json.decodeFromString<UserProfile>(jsonInput)
println(decodedProfile) // Output: UserProfile(userId=67890, name=Charlie, email=null)
}
“`
@SerialName
注解会覆盖属性本身的名称,无论序列化还是反序列化都会使用指定的名称。
5.3 忽略属性 (@Transient
)
有时候数据类中包含一些不需要序列化或反序列化的属性(例如,计算属性、缓存值或只在内存中使用的状态)。可以使用 @Transient
注解来标记这些属性。
“`kotlin
@Serializable
data class Item(val id: Int, val name: String, @Transient val isSelected: Boolean = false) {
// 计算属性,无需序列化
val displayId: String
get() = “ITEM-$id”
}
fun main() {
val item = Item(1, “Widget”, isSelected = true)
val jsonString = Json.encodeToString(item)
println(jsonString) // Output: {"id":1,"name":"Widget"} - isSelected 和 displayId 都没有出现在 JSON 中
val jsonInput = """{"id":2,"name":"Gadget"}"""
val decodedItem = Json.decodeFromString<Item>(jsonInput)
println(decodedItem) // Output: Item(id=2, name=Gadget, isSelected=false) - isSelected 使用了默认值 false
}
“`
被 @Transient
标记的属性不会参与序列化过程,反序列化时会使用属性本身的默认值(如果有的话),否则对于基本类型是其默认值(0, false),对于对象类型是 null
。
5.4 自定义序列化器 (KSerializer
)
对于一些 kotlinx.serialization
无法直接处理的类型(例如,java.util.Date
,第三方库中的类,或者你需要完全控制序列化逻辑的情况),你可以实现自定义的 KSerializer
接口。
KSerializer<T>
接口有两个核心方法:
serialize(encoder: Encoder, value: T)
: 如何将类型T
的对象value
编码到encoder
。deserialize(decoder: Decoder): T
: 如何从decoder
中解码出类型T
的对象。
此外,还需要提供 descriptor
属性,用于描述序列化结构的元数据。
示例:序列化 java.util.Date
为 ISO 8601 字符串:
“`kotlin
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.
import kotlinx.serialization.encoding.
import java.text.SimpleDateFormat
import java.util.*
// 定义一个 Date 的自定义序列化器
@Serializer(forClass = Date::class)
object DateAsLongSerializer : KSerializer
// 描述符,指定序列化后的形状 (这里是 Long)
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(“java.util.Date”, PrimitiveKind.LONG)
// 如何序列化 Date 对象
override fun serialize(encoder: Encoder, value: Date) {
// 将 Date 转换为 Long (时间戳)
encoder.encodeLong(value.time)
}
// 如何反序列化 Long 为 Date 对象
override fun deserialize(decoder: Decoder): Date {
// 将 Long (时间戳) 转换为 Date
return Date(decoder.decodeLong())
}
}
// 另一个示例:序列化 Date 为 ISO 8601 字符串
object DateAsIso8601StringSerializer : KSerializer
private val formatter = SimpleDateFormat(“yyyy-MM-dd’T’HH:mm:ss’Z'”, Locale.US).apply {
timeZone = TimeZone.getTimeZone(“UTC”)
}
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("java.util.Date", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Date) {
encoder.encodeString(formatter.format(value))
}
override fun deserialize(decoder: Decoder): Date {
return formatter.parse(decoder.decodeString())
}
}
“`
如何使用自定义序列化器?
-
应用于特定属性: 使用
@Serializable(with = YourSerializer::class)
注解。“`kotlin
@Serializable
data class Event(
val name: String,
@Serializable(with = DateAsIso8601StringSerializer::class) val timestamp: Date
)fun main() {
val event = Event(“Meeting”, Date()) // 当前时间val jsonString = Json.encodeToString(event) println(jsonString) // Output: {"name":"Meeting","timestamp":"YYYY-MM-DDTHH:MM:SSZ"} (实际是当前时间的 ISO 格式) // 假设反序列化一个 JSON 字符串 val jsonInput = """{"name":"Launch","timestamp":"2023-10-27T10:00:00Z"}""" val decodedEvent = Json.decodeFromString<Event>(jsonInput) println(decodedEvent)
}
“` -
应用于整个类: 如果自定义序列化器完全取代了类本身的默认序列化逻辑,也可以应用于类。
“`kotlin
// 假设有一个不需要暴露内部结构的类,只想序列化为一个简单的字符串
class SecretData(private val secretValue: String) {
fun getSecret(): String = secretValue
}object SecretDataSerializer : KSerializer
{
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(“SecretData”, PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: SecretData) {
encoder.encodeString(value.getSecret()) // 只序列化内部的值
}
override fun deserialize(decoder: Decoder): SecretData {
return SecretData(decoder.decodeString()) // 反序列化字符串创建对象
}
}@Serializable(with = SecretDataSerializer::class)
data class DataWithSecret(val id: Int, val data: SecretData)fun main() {
val data = DataWithSecret(1, SecretData(“my_hidden_value”))
val jsonString = Json.encodeToString(data)
println(jsonString) // Output: {“id”:1,”data”:”my_hidden_value”}
}
“` -
通过
SerializersModule
注册: 对于一些通用类型(如Date
)或者你想在全局范围内应用自定义序列化器,可以在配置Json
对象时注册。“`kotlin
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextualval appSerializersModule = SerializersModule {
// 注册 Date 的上下文序列化器
contextual(DateAsIso8601StringSerializer)
// 可以注册其他类型的序列化器
// contextual(UUID::class, UUIDSerializer) // 假设你写了一个 UUID 的序列化器
}// 使用配置了模块的 Json 对象
val appContextAwareJson = Json {
serializersModule = appSerializersModule
prettyPrint = true // 其他配置
}@Serializable
data class AnotherEvent(
val name: String,
val timestamp: Date // 注意:这里不需要 @Serializable(with = …)
)fun main() {
val event = AnotherEvent(“Meeting”, Date())
// 使用配置了模块的 Json 对象进行序列化
println(appContextAwareJson.encodeToString(event))
// 反序列化同样使用这个 Json 对象
val jsonInput = “””
{
“name”: “Launch”,
“timestamp”: “2023-10-27T10:00:00Z”
}
“””.trimIndent()
val decodedEvent = appContextAwareJson.decodeFromString(jsonInput)
println(decodedEvent)
}
“`使用
SerializersModule
注册的好处是,你可以在你的数据类中直接使用这些类型,而无需在每个属性上添加@Serializable(with = ...)
注解,让代码更简洁。对于Date
这种在很多地方使用的类型,这种方式非常方便。
5.5 处理多态性 (Polymorphism)
在面向对象编程中,我们经常处理具有继承关系的对象集合(例如,一个包含不同形状的列表)。当序列化这种结构时,仅仅保存属性是不够的,还需要保存对象的实际类型信息,以便反序列化时能够创建正确的子类实例。kotlinx.serialization
通过多态序列化支持这一点。
多态序列化通常用于:
- 密封类 (Sealed Classes): Kotlin 中表示受限继承体系的理想选择。
- 接口 (Interfaces): 序列化实现某个接口的不同类。
- 开放类 (Open Classes): 序列化具有继承关系的普通类。
要启用多态序列化,你需要在 Json
配置中注册相关的子类型。
示例:使用密封类表示不同形状
“`kotlin
import kotlinx.serialization.json.
import kotlinx.serialization.modules.
// 标记基类/接口为 @Serializable
@Serializable
sealed class Shape {
abstract val id: String
}
// 标记子类为 @Serializable
@Serializable
@SerialName(“circle”) // 使用 @SerialName 指定子类的类型标识符,这是最佳实践
data class Circle(override val id: String, val radius: Double) : Shape()
@Serializable
@SerialName(“square”) // 使用 @SerialName 指定子类的类型标识符
data class Square(override val id: String, val sideLength: Double) : Shape()
// 需要一个 SerializersModule 来注册基类和子类
val shapeModule = SerializersModule {
// 注册 Shape 的多态实现
// baseClass
// polymorphic(…) 注册具体的子类
// addSubclass
// 如果没有 @SerialName,默认使用类名作为标识符 (不推荐,易受类名重构影响)
polymorphic(Shape::class) {
subclass(Circle::class) // 注册 Circle
subclass(Square::class) // 注册 Square
}
// 或者更简洁的写法,如果子类已经标记了 @SerialName
// polymorphic(Shape::class) {
// subclass(Circle::class, Circle.serializer()) // 明确指定 serializer
// subclass(Square::class, Square.serializer()) // 明确指定 serializer
// }
// 通常编译器插件会自动生成 serializer() 方法,所以上面的 submodule 方式也很常见
}
// 使用配置了多态模块的 Json 对象
val polyJson = Json {
serializersModule = shapeModule
prettyPrint = true // 让输出更易读
// classDiscriminator = “type” // 默认的类型标识符字段名是 “type”,你也可以自定义
}
fun main() {
val shapes: List
Circle(“c1”, 10.0),
Square(“s1”, 5.0),
Circle(“c2”, 15.0)
)
// 序列化包含多态对象的列表
val jsonString = polyJson.encodeToString(shapes)
println("Serialized Shapes:")
println(jsonString)
/* Output (类似):
[
{
"type": "circle", // 默认的类型标识符字段
"id": "c1",
"radius": 10.0
},
{
"type": "square",
"id": "s1",
"sideLength": 5.0
},
{
"type": "circle",
"id": "c2",
"radius": 15.0
}
]
*/
// 反序列化
val decodedShapes = polyJson.decodeFromString<List<Shape>>(jsonString)
println("Deserialized Shapes:")
println(decodedShapes)
// 验证类型是否正确
decodedShapes.forEach { shape ->
println("Shape ID: ${shape.id}, Type: ${shape::class.simpleName}")
}
/* Output (类似):
Shape ID: c1, Type: Circle
Shape ID: s1, Type: Square
Shape ID: c2, Type: Circle
*/
}
“`
关键点:
- 基类/接口和所有可能的子类都必须标记
@Serializable
。 - 在
SerializersModule
中使用polymorphic(BaseClass::class)
块来注册所有子类。使用subclass(SubClass::class)
或subclass(SubClass::class, SubClass.serializer())
进行注册。 - 强烈推荐在子类上使用
@SerialName("type_identifier")
来指定一个明确的类型标识符,而不是依赖默认的类名,因为类名可能会重构。 - 默认情况下,
kotlinx.serialization
会在 JSON 对象中添加一个名为"type"
的字段来存储类型标识符。你可以通过Json
配置中的classDiscriminator
属性来自定义这个字段名。 - 序列化和反序列化时,必须使用配置了包含多态模块的
Json
对象。
多态序列化对于处理复杂的、面向对象的数据模型至关重要。
5.6 配置 Json
对象
Json
对象是 kotlinx.serialization-json
库的入口点,它是线程安全的,并且可以通过一个 builder 模式进行高度配置。常见的配置选项包括:
kotlin
val customJson = Json {
prettyPrint = true // 是否输出带缩进的漂亮格式 JSON
isLenient = true // 是否允许非标准 JSON 特性 (例如,不带引号的属性名,单引号字符串等)
ignoreUnknownKeys = true // 反序列化时是否忽略 JSON 中存在但在数据类中不存在的 key
coerceInputValues = true // 对于非空类型,是否将 JSON 中的 null 或类型不匹配的值强制转换为该类型的默认值 (如果存在)
useAlternativeNames = true // 是否在序列化时使用 `@SerialName` 指定的名称,反序列化时同时接受属性名和 `@SerialName` 指定的名称
encodeDefaults = false // 是否在序列化时编码默认值 (默认行为)
explicitNulls = true // 是否在序列化时包含值为 null 的可空属性 (默认是省略)
classDiscriminator = "clazz" // 自定义多态序列化时的类型标识符字段名
serializersModule = SerializersModule { /* ... */ } // 注册自定义序列化器或多态类型
// ... 还有其他一些配置选项
}
理解这些配置选项非常重要,特别是当你需要与现有的、可能不完全标准的 JSON API 交互时:
prettyPrint
: 用于调试或生成可读性强的输出,不建议用于生产环境的网络传输(增加体积)。isLenient
: 如果你处理的 JSON 格式不完全遵循标准(例如,来自一些旧系统或JavaScriptJSON.parse
的宽松输出),这个选项很有用。但使用时需要小心,因为它可能掩盖格式问题。ignoreUnknownKeys
: 当你的数据类是 JSON 数据的子集时,这个选项非常有用。它可以让你只定义你关心的字段,而忽略 JSON 中的其他字段,避免反序列化失败。coerceInputValues
: 可以帮助处理 JSON 中意外的null
或类型不匹配的情况,但过度依赖可能导致数据丢失或逻辑错误。explicitNulls
: 默认情况下,值为null
的可空属性在 JSON 中会被省略。设置explicitNulls = true
会让这些属性以key: null
的形式出现在 JSON 中。这取决于接收方对null
的处理方式。
始终使用同一个配置好的 Json
对象进行相关的序列化和反序列化操作,特别是当你使用了 serializersModule
或其他全局配置时。
5.7 集合、枚举和内置类型
kotlinx.serialization
天然支持 Kotlin 的标准集合类型(如 List
, Set
, Map
)以及基本数据类型 (Int
, String
, Boolean
, Double
等)。
“`kotlin
@Serializable
data class DataWithCollections(
val ids: List
val tags: Set
val metadata: Map
)
@Serializable
enum class Status {
ACTIVE, INACTIVE, PENDING
}
@Serializable
data class ItemWithStatus(val name: String, val status: Status)
fun main() {
val collections = DataWithCollections(
ids = listOf(1, 2, 3),
tags = setOf(“kotlin”, “serialization”),
metadata = mapOf(“version” to “1.0”, “author” to “kotlinx”)
)
println(Json.encodeToString(collections))
// Output: {"ids":[1,2,3],"tags":["kotlin","serialization"],"metadata":{"version":"1.0","author":"kotlinx"}}
val item = ItemWithStatus("Task", Status.ACTIVE)
println(Json.encodeToString(item))
// Output: {"name":"Task","status":"ACTIVE"}
val jsonStatus = """{"name":"Report","status":"PENDING"}"""
val decodedItem = Json.decodeFromString<ItemWithStatus>(jsonStatus)
println(decodedItem) // Output: ItemWithStatus(name=Report, status=PENDING)
}
“`
对于枚举,默认情况下会序列化为枚举常量的名称字符串。你可以使用 @SerialName
为枚举常量指定不同的序列化名称。
“`kotlin
@Serializable
enum class Role {
@SerialName(“admin”) ADMIN,
@SerialName(“user”) USER,
GUEST // 没有注解,默认使用名称 “GUEST”
}
@Serializable
data class Member(val name: String, val role: Role)
fun main() {
val adminMember = Member(“Boss”, Role.ADMIN)
println(Json.encodeToString(adminMember)) // Output: {“name”:”Boss”,”role”:”admin”}
val guestMember = Member("Visitor", Role.GUEST)
println(Json.encodeToString(guestMember)) // Output: {"name":"Visitor","role":"GUEST"}
val jsonInput = """{"name":"Alice","role":"user"}"""
val decodedMember = Json.decodeFromString<Member>(jsonInput)
println(decodedMember) // Output: Member(name=Alice, role=USER)
}
“`
6. 与其他库和框架的集成
kotlinx.serialization
作为官方库,与 Kotlin 生态中的其他库和框架有很好的集成。
- Ktor: Ktor 是 Kotlin 官方的异步框架,用于构建服务器和客户端。它内置了对
kotlinx.serialization
的 ContentNegotiation 支持,可以非常方便地处理请求和响应体的序列化/反序列化。只需添加相应的 ContentNegotiation 特性并配置序列化器即可。 - Retrofit (Android/JVM): Retrofit 是一个流行的类型安全的 HTTP 客户端。你可以通过自定义
Converter.Factory
来集成kotlinx.serialization
,使其能够处理 JSON 请求体和响应体。有一些社区提供的库可以帮助你快速实现这一点。 - 数据库/存储: 虽然
kotlinx.serialization
本身不是数据库库,但你可以使用它将 Kotlin 对象序列化为字符串(如 JSON)或字节数组(如 Protobuf, CBOR),然后将这些序列化的数据存储到数据库字段中(例如,存储复杂对象的配置或元数据)。
7. 优势与考虑
优势总结:
- Kotlin-native & Idiomatic: 与 Kotlin 语言特性完美结合。
- 高性能 & 类型安全: 编译器插件生成代码,避免反射开销。
- 多平台支持: 适用于 JVM, JS, Native 等。
- 零样板代码: 对于简单数据类,注解即可搞定。
- 丰富的特性: 支持可空性、默认值、自定义名称、忽略属性、自定义序列化器、多态等。
- 多格式支持: 通过模块化支持多种流行的序列化格式。
需要考虑的点:
- 编译器插件依赖: 必须应用 Kotlin 序列化插件,这可能会对编译时间产生微小影响(通常可以忽略)。
- 某些场景的额外配置: 处理多态或需要高度自定义序列化逻辑时,需要编写
SerializersModule
或自定义KSerializer
,比纯反射库(如 Gson)在极少数简单场景下可能稍显复杂,但换来了性能和类型安全。 - XML 支持: 官方目前没有成熟的 XML 模块,如果你的主要需求是 XML 序列化,可能需要寻找其他库或社区实现的模块。
8. 最佳实践
- 优先使用数据类 (
data class
):kotlinx.serialization
对数据类支持最好,自动生成代码。 - 始终标记
@Serializable
: 确保需要序列化的类及其嵌套类都被正确标记。 - 利用
@SerialName
和@Transient
: 清晰地控制序列化结构,与 JSON 格式对齐,忽略不需要的属性。 - 明智地使用
@EncodeDefault
: 只在需要包含默认值的属性时使用,避免不必要的输出。 - 配置
Json
对象: 根据你的需求(如漂亮打印、忽略未知字段等)创建和重用配置好的Json
实例。对于包含多态或自定义序列化器的场景,必须使用配置了相应SerializersModule
的Json
实例。 - 使用
SerializersModule
注册通用序列化器或多态类型: 避免在多处重复使用@Serializable(with = ...)
注解,保持代码简洁。 - 为多态类型定义
@SerialName
: 使用明确的类型标识符,提高鲁棒性。 - 处理遗留或复杂格式时,编写自定义
KSerializer
: 它提供了完全的控制权。
9. 总结
kotlinx.serialization
是 Kotlin 官方提供的一个功能强大、灵活且与语言特性深度集成的序列化库。通过依赖 Kotlin 编译器插件,它提供了高性能、类型安全且几乎无需样板代码的序列化解决方案。
从简单的 @Serializable
注解到处理多态、自定义序列化器的复杂场景,kotlinx.serialization
提供了丰富的工具来满足各种序列化需求。它对 Kotlin 可空性、默认值和数据类的良好支持,以及其多平台特性,使其成为 Kotlin 项目中处理数据序列化的首选库。
无论你是开发后端服务、Android 应用、桌面应用还是多平台项目,掌握 kotlinx.serialization
都将大大提高你在数据处理方面的效率和代码质量。开始在你的项目中使用它吧,你会体验到 Kotlin 带来的便利与强大。