1. 概述

kotlinx-serialization 是 Kotlin 官方推出的一个序列化库,支持将 Kotlin 对象序列化为 JSON、Protobuf、CBOR 等多种格式。

它是一个开源项目,提供了简洁易用的 API,适用于无反射的序列化场景。Kotlin 1.4.0 开始引入了 JSON 序列化的稳定版本,取代了之前使用的 kotlinx-serialization-runtime 库,但其他格式的序列化目前仍未正式稳定。

本教程将重点介绍如何使用 kotlinx-serialization 的 JSON 功能。我们将学习如何在项目中使用它,以及如何编写自定义序列化器来处理更复杂的结构。

2. 开始使用 kotlinx-serialization

要使用该库,首先需要将其依赖添加到项目的 pom.xml 文件中。在此之前,先定义兼容的版本号:

<properties>
    <kotlin.version>1.8.10</kotlin.version>
    <serialization.version>1.5.0</serialization.version>
</properties>

接着,在 Kotlin 编译插件中添加对 kotlinx-serialization 的支持:

<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>${kotlin.version}</version>
            <executions>
                <execution>
                    <id>compile</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <compilerPlugins>
                    <plugin>kotlinx-serialization</plugin>
                </compilerPlugins>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>org.jetbrains.kotlin</groupId>
                    <artifactId>kotlin-maven-serialization</artifactId>
                    <version>${kotlin.version}</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

这个插件负责为使用 @Serializable 注解的类自动生成序列化代码。

最后,添加运行时依赖:

<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-serialization-json</artifactId>
    <version>${serialization.version}</version>
</dependency>

添加完这些配置后,就可以在项目中使用 kotlinx-serialization 了。

3. 序列化对象

第一步是定义一个需要被序列化的 Kotlin 数据类。我们创建一个简单的 SerializablePerson 类,包含 firstNamelastNameage 三个属性:

@Serializable
data class SerializablePerson(
    val firstName: String,
    val lastName: String,
    val age: Int,
) {
    init {
        require(firstName.isNotEmpty()) { "First name can't be empty" }
    }
    
    val fullName: String
        get() = "$firstName $lastName"
    
    val id by ::lastName
}

注意: 必须为类添加 @Serializable 注解,kotlinx-serialization 才会生成对应的序列化代码。

在这个类中我们还添加了两个没有 backing field 的属性:fullName(运行时计算)和 id(委托属性)。kotlinx-serialization 只会序列化具有 backing field 的属性,因此这两个属性不会被包含在输出中。

使用 Json.encodeToString() 方法即可将对象序列化为 JSON:

val person = SerializablePerson("John", "Doe", 30)
val json = Json.encodeToString(person)

输出结果如下(注意 fullNameid 并未出现):

{
  "firstName": "John",
  "lastName": "Doe",
  "age": 30
}

4. 反序列化对象

反序列化也很简单,使用 Json.decodeFromString() 方法即可:

val json = """{"firstName":"John","lastName":"Doe","age":30}"""
val person = Json.decodeFromString<SerializablePerson>(json)

⚠️ 但是,如果类中包含委托属性(如 id),反序列化会失败!

下面是一个失败的测试示例:

@Test
fun `fails to deserialize string to object with delegated property`() {
    val json = """{"firstName":"John","lastName":"Doe","age":30}"""
    val exception = assertFailsWith<NoSuchFieldError> {
        Json.decodeFromString<SerializablePerson>(json)
    }
}

解决方法是去掉委托属性,重新定义一个干净的类:

@Serializable
data class DeserializablePerson(val firstName: String, val lastName: String, val age: Int) {
    init {
        require(firstName.isNotEmpty()) { "First name can't be empty" }
    }
}

✅ 反序列化成功:

@Test
fun `deserializes string to object`() {
    val json = """{"firstName":"John","lastName":"Doe","age":30}"""
    val person = Json.decodeFromString<DeserializablePerson>(json)
    assertEquals(DeserializablePerson("John", "Doe", 30), person)
}

⚠️ 初始化逻辑也会被调用,如果初始化条件不满足,会抛出异常:

@Test
fun `fails to deserialize string to object when json input is incorrect`() {
    val json = """{"firstName":"","lastName":"Doe","age":30}"""
    assertFailsWith<IllegalArgumentException> {
        Json.decodeFromString<DeserializablePerson>(json)
    }
}

5. 使用自定义序列化器

虽然 kotlinx-serialization 内置支持很多类型,但某些复杂类型(如 LocalDateTime)需要我们自己实现序列化逻辑。

5.1. 创建自定义序列化器

实现自定义序列化器需继承 KSerializer 接口,并实现 serialize()deserialize() 方法。

以下是一个针对 LocalDateTime 的自定义序列化器:

object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.String)

    override fun serialize(encoder: Encoder, value: LocalDateTime) {
        encoder.encodeString(value.toString())
    }

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

要使用这个自定义序列化器,我们需要将其注册到 Json 配置中:

val json = Json {
    serializersModule = SerializersModule {
        contextual(LocalDateTimeSerializer)
    }
}

然后就可以序列化 LocalDateTime 类型了:

@Test
fun `serializes LocalDateTime with registered serializer`() {
    val dateTime = LocalDateTime.parse("2023-04-24T15:30:00.123")
    val serializedDateTime = json.encodeToString(dateTime)
    assertEquals("\"2023-04-24T15:30:00.123\"", serializedDateTime)
}

反序列化也一样:

@Test
fun `deserializes LocalDateTime with registered serializer`() {
    val dateTime = LocalDateTime.parse("2023-04-24T15:30:00.123")
    val deserializedDateTime = json.decodeFromString<LocalDateTime>("\"2023-04-24T15:30:00.123\"")
    assertEquals(dateTime, deserializedDateTime)
}

如果我们在数据类中直接使用该类型,必须使用 @Serializable(with = ...) 明确指定序列化器:

@Test
fun `serializes LocalDateTime using annotation declared serializer`() {
    @Serializable
    data class LocalDateTimeWrapper(
        @Serializable(with = LocalDateTimeSerializer::class)
        val dateTime: LocalDateTime,
    )

    val wrapper = LocalDateTimeWrapper(LocalDateTime.parse("2023-04-24T15:30:00.123"))
    val json = Json.encodeToString(wrapper)
    assertEquals("{\"dateTime\":\"2023-04-24T15:30:00.123\"}", json)
}

⚠️ 如果使用 @Contextual 但未注册序列化器,则会抛出 SerializationException

@Test
fun `throws exception serializing LocalDateTime with unregistered contextual serializer`() {
    @Serializable
    data class LocalDateTimeWrapper(
        @Contextual
        val dateTime: LocalDateTime,
    )

    val wrapper = LocalDateTimeWrapper(LocalDateTime.parse("2023-04-24T15:30:00.123"))
    assertThrows<SerializationException> {
        Json.encodeToString(wrapper)
    }
}

5.2. 使用第三方库

也可以使用 JetBrains 提供的 kotlinx-datetime 库,它已经内置了对 kotlinx-serialization 的支持。

添加依赖:

<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-datetime-jvm</artifactId>
    <version>0.4.0</version>
</dependency>

现在可以直接使用 kotlinx.datetime.LocalDateTime 类型进行序列化和反序列化,无需手动注册:

import kotlinx.datetime.LocalDateTime

注意: 该库使用的是 kotlinx.datetime 包下的类型,而非标准库的 java.time

6. 总结

kotlinx-serialization 是一个功能强大、使用方便的 Kotlin 序列化库,支持多种格式和常见类型。

  • ✅ 支持自动代码生成,无需反射
  • ✅ 可以自定义序列化器处理复杂类型
  • ✅ 支持通过注解或运行时注册自定义逻辑
  • ✅ JetBrains 提供了如 kotlinx-datetime 等扩展库,简化开发

如果你正在开发 Kotlin 项目并需要序列化功能,kotlinx-serialization 是一个非常值得考虑的选择。


原始标题:An Introduction to kotlinx-serialization Project