03.Kotlin Serialization - 认识序列化器

前言

前面我们学习了如何通过注解来影响属性的序列化过程,但要实现更精细的控制,就需要深入了解序列化器(Serializer)了。本文将全面介绍序列化器的概念、类型和创建方式,帮助您完全掌握类的序列化和反序列化逻辑。

序列化器是Kotlin Serialization框架的核心组件,它定义了对象如何分解为基本属性以及如何从这些属性重新构建对象。虽然格式(如JSON)控制着数据的编码方式,但序列化器决定着对象的结构化表示。从设计上来说,序列化器可以很方便地支持多种序列化格式,同一个序列化器可以无缝地在JSON、ProtoBuf、CBOR等不同格式之间切换使用。

一、序列化器基础认识

要理解序列化器,首先需要了解其接口体系和工作原理。

1.1 KSerializer接口体系

所有序列化器都基于以下三层接口体系:

kotlin 复制代码
// 序列化策略 - 定义如何将对象转换为数据
interface SerializationStrategy<in T> {
    val descriptor: SerialDescriptor
    fun serialize(encoder: Encoder, value: T)
}

// 反序列化策略 - 定义如何从数据重建对象  
interface DeserializationStrategy<T> {
    val descriptor: SerialDescriptor
    fun deserialize(decoder: Decoder): T
}

interface KSerializer<T> : SerializationStrategy<T>, DeserializationStrategy<T> {
    override val descriptor: SerialDescriptor
}

1.2 核心组件说明

这些接口各司其职,共同构成了完整的序列化体系:

  • SerializationStrategy:负责将对象转换为数据格式(对象 → 数据)
  • DeserializationStrategy:负责从数据格式重建对象(数据 → 对象)
  • SerialDescriptor:描述数据结构信息,为编码/解码过程提供元数据支持
  • KSerializer:组合了上述两个策略,是序列化器的统一接口

1.3 工作流程

scss 复制代码
+---------+                 +------------+                +-------------+
| Objects | ──serialize()─> | Serializer | ──encode()──>  | JSON/Binary |
+---------+                 +------------+                +-------------+
     ↑                            │                              │
     │                            │                              │
     └─────deserialize()───────-──┴─────────decode()─────────────┘

二、SerialDescriptor详解

SerialDescriptor是序列化器的"说明书",它描述了序列化数据的结构信息,让格式实现知道如何处理数据,同时支持格式协商、模式生成和序列化优化。理解SerialDescriptor是掌握自定义序列化器的关键。

2.1 descriptor包含的信息

SerialDescriptor主要描述两个层面的信息:

结构层面 :类型名称、数据种类(kind)、包含多少个字段(elementsCount)
字段层面:字段的名称、字段的SerialDescriptor、位置索引,以及字段的注解、可选性等元数据

2.2 常见descriptor分类

基础类型

基础类型使用简单的标识描述:

kotlin 复制代码
PrimitiveDescriptor(kotlin.Int)        // 整数
PrimitiveDescriptor(kotlin.String)     // 字符串
PrimitiveDescriptor(kotlin.Boolean)    // 布尔值

复合类型

复合类型描述包含字段信息:

kotlin 复制代码
// Student类的描述符
org.example.Student(name: kotlin.String, age: kotlin.Int)

// 嵌套对象的描述符
org.example.Student(name: kotlin.String, address: org.example.Address)

集合类型

集合类型描述元素类型:

kotlin 复制代码
// List<String>的描述符
kotlin.collections.ArrayList(PrimitiveDescriptor(kotlin.String))

// Map<String, Int>的描述符  
kotlin.collections.LinkedHashMap(
    PrimitiveDescriptor(kotlin.String), 
    PrimitiveDescriptor(kotlin.Int)
)

泛型类型

泛型类型展示具体的类型参数:

kotlin 复制代码
@Serializable
class Wrapper<T>(val value: T)

// Wrapper<String>
org.example.Wrapper(value: PrimitiveDescriptor(kotlin.String))

// Wrapper<List<Int>>
org.example.Wrapper(value: kotlin.collections.ArrayList(PrimitiveDescriptor(kotlin.Int)))

// Wrapper<Student>
org.example.Wrapper(value: org.example.Student)

2.3 常用descriptor实现方式

PrimitiveSerialDescriptor

基础类型的描述符:

kotlin 复制代码
// 字符串类型
PrimitiveSerialDescriptor("org.example.Color", PrimitiveKind.STRING)

// 长整型
PrimitiveSerialDescriptor("org.example.Color", PrimitiveKind.LONG)

buildClassSerialDescriptor

手工构建复合对象的描述符:

kotlin 复制代码
buildClassSerialDescriptor("org.example.Student") {
    element<String>("name")           // 字符串字段
    element<Int>("age")              // 整数字段
    element<List<String>>("hobbies") // 列表字段
}

SerialDescriptor包装

序列化器委托:

kotlin 复制代码
// 委托给内置序列化器
SerialDescriptor("org.example.Color", IntArraySerializer().descriptor)

// 委托给自定义代理类
SerialDescriptor("org.example.Color", ColorSurrogate.serializer().descriptor)

自动生成的描述符

插件自动生成的类会有对应的描述符:

kotlin 复制代码
@Serializable
class Student(val name: String, val grade: Int)

// 自动生成的描述符:Student(name: kotlin.String, grade: kotlin.Int)

集合描述符

集合类型需要指定元素类型:

kotlin 复制代码
// List描述符
ListSerializer(String.serializer()).descriptor

// Map描述符  
MapSerializer(String.serializer(), Int.serializer()).descriptor

// Set描述符
SetSerializer(Long.serializer()).descriptor

2.4 核心概念理解

SerialDescriptor是连接对象结构和数据格式的桥梁,其组织方式有一个重要特点:

树状结构概念:SerialDescriptor的结构可以理解为树状结构,类型本身是根节点,字段是子节点。字段可能是叶子节点(如基础类型),也可能包含自己的子节点(如嵌套对象)。重要的是,所有叶子节点的类型都必须是框架内置支持的基础类型。

三、自动生成序列化器

了解了序列化器的基本概念后,让我们看看如何获取和使用序列化器。最简单的方式就是使用框架自动生成的序列化器。

3.1 插件自动生成

每个标记了@Serializable注解的类,都会自动生成一个KSerializer实例:

kotlin 复制代码
@Serializable
class Student(val name: String, val language: String)

// 获取序列化器
val serializer = Student.serializer()

3.2 泛型类的序列化器

泛型类的序列化器需要提供类型参数的序列化器:

kotlin 复制代码
@Serializable
class Box<T>(val contents: T)

// 需要传入类型参数的序列化器
val stringBoxSerializer = Box.serializer(String.serializer())

// 通用函数(自动推断类型)
val studentBoxSerializer: KSerializer<Box<Student>> = serializer()

3.3 内置序列化器

框架为基础类型和集合类型提供了内置序列化器:

kotlin 复制代码
// 基础类型序列化器
Int.serializer()
String.serializer()
Boolean.serializer()

// 集合序列化器(需要显式构造)
ListSerializer(String.serializer())
SetSerializer(Int.serializer())
MapSerializer(String.serializer(), Long.serializer())

// 通用函数(自动推断类型)
val listSerializer: KSerializer<List<String>> = serializer()
val mapSerializer: KSerializer<Map<String, Int>> = serializer()

四、手写自定义序列化器

当自动生成的序列化器无法满足需求时,我们需要手写自定义序列化器。本节将通过具体示例来演示各种实现方式。

本节示例类:

kotlin 复制代码
package org.example

@Serializable
class Color(val rgb: Int)

手写序列化器需要理解两个核心部分:如何构建descriptor描述数据结构,以及如何实现serialize/deserialize方法。

4.1 构建SerialDescriptor

前面章节已经详细介绍了SerialDescriptor以及构建方式,此处不再赘述。

4.2 实现serialize和deserialize

基础类型序列化

对于基础类型,直接使用encoder/decoder的基础方法。下面的示例演示如何将Color类序列化为字符串:

kotlin 复制代码
object ColorAsStringSerializer : KSerializer<Color> {
    
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("org.example.Color", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: Color) {
        val string = value.rgb.toString()
        encoder.encodeString(string)
    }

    override fun deserialize(decoder: Decoder): Color {
        val string = decoder.decodeString()
        return Color(string.toInt())
    }
}

委托序列化

将序列化工作委托给其他序列化器。下面的示例演示如何将Color类序列化为Int数组:

kotlin 复制代码
class ColorIntArraySerializer : KSerializer<Color> {

    // 委托序列化器
    private val delegateSerializer = IntArraySerializer()

    override val descriptor = SerialDescriptor("org.example.Color", delegateSerializer.descriptor)

    override fun serialize(encoder: Encoder, value: Color) {
        // 转换为RGB数组
        val rgbArray = intArrayOf(
            (value.rgb shr 16) and 0xFF,
            (value.rgb shr 8) and 0xFF,
            value.rgb and 0xFF
        )
        // 委托给IntArray序列化器
        encoder.encodeSerializableValue(delegateSerializer, rgbArray)
    }

    override fun deserialize(decoder: Decoder): Color {
        // 委托给IntArray序列化器解析
        val rgbArray = decoder.decodeSerializableValue(delegateSerializer)
        return Color((rgbArray[0] shl 16) or (rgbArray[1] shl 8) or rgbArray[2])
    }
}

代理类序列化

代理是委托的一种特殊形式,本质上都是将序列化工作交给其他序列化器完成。这种方式会创建一个代理类,通过代理类间接获取序列化器,从而简化复合对象的序列化:

kotlin 复制代码
@Serializable
private class ColorSurrogate(val r: Int, val g: Int, val b: Int)

object ColorSerializer : KSerializer<Color> {

    override val descriptor = SerialDescriptor("org.example.Color", ColorSurrogate.serializer().descriptor)

    override fun serialize(encoder: Encoder, value: Color) {
        // 转换为代理对象
        val surrogate = ColorSurrogate(
            (value.rgb shr 16) and 0xff,
            (value.rgb shr 8) and 0xff,
             value.rgb and 0xff
        )
        // 委托给代理类序列化器
        encoder.encodeSerializableValue(ColorSurrogate.serializer(), surrogate)
    }

    override fun deserialize(decoder: Decoder): Color {
        // 委托给代理类序列化器解析
        val surrogate = decoder.decodeSerializableValue(ColorSurrogate.serializer())
        return Color((surrogate.r shl 16) or (surrogate.g shl 8) or surrogate.b)
    }
}

完全手写序列化

对于特殊场景,可以完全手写序列化逻辑。这种方式提供了最大的灵活性,但迭代维护成本较高,应尽量避免使用:

kotlin 复制代码
object ColorAsObjectSerializer : KSerializer<Color> {
    override val descriptor = buildClassSerialDescriptor("org.example.Color") {
        element<Int>("r")
        element<Int>("g") 
        element<Int>("b")
    }

    override fun serialize(encoder: Encoder, value: Color) =
        encoder.encodeStructure(descriptor) {
            // 按索引顺序编码各个元素
            encodeIntElement(descriptor, 0, (value.rgb shr 16) and 0xff)  // r
            encodeIntElement(descriptor, 1, (value.rgb shr 8) and 0xff)   // g
            encodeIntElement(descriptor, 2, value.rgb and 0xff)           // b
        }

    override fun deserialize(decoder: Decoder): Color =
        decoder.decodeStructure(descriptor) {
            var r = -1; var g = -1; var b = -1
            // 循环解码,支持任意顺序
            while (true) {
                when (val index = decodeElementIndex(descriptor)) {
                    0 -> r = decodeIntElement(descriptor, 0)
                    1 -> g = decodeIntElement(descriptor, 1)
                    2 -> b = decodeIntElement(descriptor, 2)
                    CompositeDecoder.DECODE_DONE -> break
                    else -> error("Unexpected index: $index")
                }
            }
            Color((r shl 16) or (g shl 8) or b)
        }
}

泛型序列化器

泛型序列化器需要接受类型参数的序列化器。下面的示例演示如何在序列化时去掉Wrapper这层包装,反序列化时重新包装:

kotlin 复制代码
// 通过注解参数指定序列化器
@Serializable(with = WrapperSerializer::class)
data class Wrapper<T>(val value: T)

class WrapperSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Wrapper<T>> {
    
    override val descriptor = SerialDescriptor("org.example.Wrapper", dataSerializer.descriptor)
    
    override fun serialize(encoder: Encoder, value: Wrapper<T>) = 
        // 直接委托给内容的序列化器
        dataSerializer.serialize(encoder, value.value)
    
    override fun deserialize(decoder: Decoder) = 
        // 解析内容后包装为Wrapper
        Wrapper(dataSerializer.deserialize(decoder))
}

总结

本文深入介绍了Kotlin Serialization中序列化器的核心概念和创建方式。我们从接口体系开始,学习了SerialDescriptor作为序列化器"说明书"的重要作用,理解了其树状结构的组织方式。然后从自动生成的序列化器到手写自定义序列化器,掌握了基础类型、委托、代理类、完全手写和泛型等多种实现策略,为灵活使用Kotlin Serialization奠定了坚实基础。

在下一篇文章中,我们将学习如何在实际项目中使用这些序列化器,包括绑定方式、配置优先级和动态序列化等实用技巧。

相关推荐
前行的小黑炭5 小时前
【Android】 Context使用不当,存在内存泄漏,语言不生效等等
android·kotlin·app
前行的小黑炭6 小时前
【Android】CoordinatorLayout详解;实现一个交互动画的效果(上滑隐藏,下滑出现);附例子
android·kotlin·app
卡尔特斯18 小时前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin
Kapaseker1 天前
每个Kotlin开发者应该掌握的最佳实践,第二趴
kotlin
alexhilton2 天前
面向开发者的系统设计:像建筑师一样思考
android·kotlin·android jetpack
用户092 天前
Gradle Cache Entries 深度探索
android·java·kotlin
叽哥2 天前
Kotlin学习第 9 课:Kotlin 实战应用:从案例到项目
android·java·kotlin
叽哥3 天前
Kotlin学习第 8 课:Kotlin 进阶特性:简化代码与提升效率
android·java·kotlin
Kapaseker3 天前
每个Kotlin开发者应该掌握的最佳实践,第一趴
kotlin