Kotlin 数据序列化:使用 Kotlin Serialization – wiki基地


Kotlin 数据序列化:使用 kotlinx.serialization 库深度解析

在现代软件开发中,数据的传输和存储无处不在。无论是网络请求、本地存储、进程间通信还是配置文件,我们都需要将内存中的数据结构(对象)转换为某种格式进行传输或存储,然后再将其恢复成内存中的数据结构。这个过程就是 序列化(Serialization)反序列化(Deserialization)

对于使用 Kotlin 开发的应用程序而言,选择一个高效、灵活且与语言特性紧密集成的序列化库至关重要。kotlinx.serialization 就是 JetBrains 官方提供的解决方案,它是一个多平台序列化库,支持多种格式,并且与 Kotlin 语言的特性(如数据类、可空性、密封类等)无缝集成。

本文将深入探讨 kotlinx.serialization 库的使用,从基础入门到高级特性,帮助你全面掌握这个强大的工具。

1. 为什么选择 kotlinx.serialization

在 Kotlin 生态系统中,存在许多成熟的序列化库,比如 Jackson、Gson 等,它们主要来源于 Java 生态。然而,kotlinx.serialization 具有一些独特的优势:

  1. Kotlin 原生支持: 它是一个 Kotlin-first 的库,设计时就考虑了 Kotlin 的语言特性。例如,它能很好地处理 Kotlin 的可空类型、默认参数以及数据类。
  2. 多平台: kotlinx.serialization 支持 Kotlin/JVM, Kotlin/JS, Kotlin/Native 等多个平台,这对于开发多平台应用(KMM – Kotlin Multiplatform Mobile)非常有益,可以共享序列化逻辑。
  3. 编译器插件: kotlinx.serialization 的核心是一个 Kotlin 编译器插件。这个插件会在编译时为标记了 @Serializable 注解的类自动生成序列化和反序列化的代码。这带来了以下好处:
    • 性能: 避免了运行时反射,提高了序列化和反序列化的速度。
    • 类型安全: 编译时生成代码,可以提前发现潜在的序列化问题。
    • 零样板代码: 对于简单的数据类,只需要一个注解即可实现序列化,无需手动编写序列化逻辑。
  4. 多种格式支持: 库本身是格式无关的,通过不同的模块支持多种数据格式,如 JSON (最常用)、Protobuf、CBOR 等。
  5. 高度可配置和扩展: 提供了丰富的 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())
}

}
“`

如何使用自定义序列化器?

  1. 应用于特定属性: 使用 @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)
    

    }
    “`

  2. 应用于整个类: 如果自定义序列化器完全取代了类本身的默认序列化逻辑,也可以应用于类。

    “`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”}
    }
    “`

  3. 通过 SerializersModule 注册: 对于一些通用类型(如 Date)或者你想在全局范围内应用自定义序列化器,可以在配置 Json 对象时注册。

    “`kotlin
    import kotlinx.serialization.modules.SerializersModule
    import kotlinx.serialization.modules.contextual

    val 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(“circle”) 作为类型标识符
// 如果没有 @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 = listOf(
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 格式不完全遵循标准(例如,来自一些旧系统或JavaScript JSON.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 实例。对于包含多态或自定义序列化器的场景,必须使用配置了相应 SerializersModuleJson 实例。
  • 使用 SerializersModule 注册通用序列化器或多态类型: 避免在多处重复使用 @Serializable(with = ...) 注解,保持代码简洁。
  • 为多态类型定义 @SerialName: 使用明确的类型标识符,提高鲁棒性。
  • 处理遗留或复杂格式时,编写自定义 KSerializer: 它提供了完全的控制权。

9. 总结

kotlinx.serialization 是 Kotlin 官方提供的一个功能强大、灵活且与语言特性深度集成的序列化库。通过依赖 Kotlin 编译器插件,它提供了高性能、类型安全且几乎无需样板代码的序列化解决方案。

从简单的 @Serializable 注解到处理多态、自定义序列化器的复杂场景,kotlinx.serialization 提供了丰富的工具来满足各种序列化需求。它对 Kotlin 可空性、默认值和数据类的良好支持,以及其多平台特性,使其成为 Kotlin 项目中处理数据序列化的首选库。

无论你是开发后端服务、Android 应用、桌面应用还是多平台项目,掌握 kotlinx.serialization 都将大大提高你在数据处理方面的效率和代码质量。开始在你的项目中使用它吧,你会体验到 Kotlin 带来的便利与强大。


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部