04.Kotlin Serialization - 序列化器的使用

前言

掌握了序列化器的基本概念和创建方式后,接下来需要学会如何在实际项目中灵活使用它们。本文将系统介绍序列化器的绑定方式、配置策略和动态序列化等实用技巧,帮助您在复杂的业务场景中游刃有余。

一、自动配置(默认行为)

在大多数情况下,我们无需显式指定序列化器,Kotlin Serialization 会根据类型自动选择合适的序列化器。这是最简单也是最常用的方式。

1.1 基本类型自动配置

对于 Kotlin 的基本类型和标准库类型,编译器插件会自动生成或选择对应的序列化器:

kotlin 复制代码
@Serializable
data class User(
    val id: Int,           // 自动使用 Int.serializer()
    val name: String,      // 自动使用 String.serializer()
    val isActive: Boolean, // 自动使用 Boolean.serializer()
    val score: Double      // 自动使用 Double.serializer()
)

// 无需指定任何序列化器,自动配置
val json = Json.encodeToString(User(1, "Alice", true, 95.5))

1.2 集合类型自动配置

集合类型会根据元素类型自动选择序列化器:

kotlin 复制代码
@Serializable
data class Course(
    val title: String,
    val tags: List<String>,        // 自动使用 ListSerializer(String.serializer())
    val scores: Map<String, Int>,  // 自动使用 MapSerializer(String.serializer(), Int.serializer())
    val optionalInfo: String?      // 自动使用 nullable String serializer
)

1.3 嵌套类型自动配置

对于嵌套的自定义类型,只要都标记了 @Serializable,就会自动配置:

kotlin 复制代码
@Serializable
data class Address(val city: String, val country: String)

@Serializable
data class Person(
    val name: String,
    val address: Address  // 自动使用 Address 的生成序列化器
)

1.4 自动配置的限制

自动配置有一些限制,以下情况需要显式指定序列化器:

  • 第三方库类型(如 java.util.Date
  • 接口和抽象类,后续章节讲解
  • 需要特殊序列化格式的类型
kotlin 复制代码
@Serializable
data class Event(
    val name: String,
    // val timestamp: Date,  // ❌ 编译错误:Date 没有序列化器
    // val data: Any,        // ❌ 编译错误:Any 没有序列化器
)

二、序列化器绑定

当自动配置无法满足需求时,需要显式绑定序列化器。Kotlin Serialization 提供了多种绑定方式,从临时使用到全局配置,满足不同的使用场景。

2.1 手动传递

最直接的使用方式,适用于临时需要或测试场景:

kotlin 复制代码
// 定义序列化器
object DateAsLongSerializer : KSerializer<Date> {
    // 实现细节...
}

// 手动传递序列化器
val json = Json.encodeToString(DateAsLongSerializer, Date())
val decoded = Json.decodeFromString(DateAsLongSerializer, json)

2.2 属性级别绑定

通过 @Serializable(with = ...) 注解为特定属性指定序列化器:

kotlin 复制代码
@Serializable
data class Event(
    val name: String,
    @Serializable(with = DateAsLongSerializer::class)
    val timestamp: Date,
)

// 无需手动指定,属性注解自动生效
val json = Json.encodeToString(Event("Meeting", Date()))

2.3 类注解绑定

通过 @Serializable(with = ...) 注解为整个类指定序列化器:

kotlin 复制代码
// 为整个类指定序列化器
@Serializable(with = DateAsLongSerializer::class)
data class CustomDate(val timestamp: Long)

@Serializable
data class Event(
    val name: String,
    val timestamp: CustomDate  // 使用类注解指定的序列化器
)

2.4 泛型绑定

为泛型参数指定序列化器:

kotlin 复制代码
@Serializable
data class Schedule(
    val events: List<@Serializable(DateAsLongSerializer::class) Date>
)

2.5 文件级别绑定

使用 @file:UseSerializers 为整个文件指定默认序列化器:

kotlin 复制代码
@file:UseSerializers(DateAsLongSerializer::class)

@Serializable
data class Event(val timestamp: Date)  // 自动使用 DateAsLongSerializer

@Serializable  
data class Meeting(val startTime: Date, val endTime: Date)  // 都使用 DateAsLongSerializer

2.6 别名绑定

通过 typealias 创建序列化类型,可以在别名上再添加注解,添加的注解优先级高于别名本身的配置:

kotlin 复制代码
// 定义类型别名
typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date

@Serializable
data class Event(
    val name: String,
    val timestamp: DateAsLong,  // 使用类型别名
    
    // 在别名基础上再添加注解,优先级更高
    @Serializable(with = DateAsStringSerializer::class)
    val createdAt: DateAsLong  // 这里会使用 DateAsStringSerializer 而不是 DateAsLongSerializer
)

三、序列化器动态配置

使用 @Contextual 注解标记需要运行时确定序列化器的字段。运行时在上下文中匹配对应的序列化器,上下文作用域限制在 Json 实例范围内。

3.1 上下文序列化

kotlin 复制代码
@Serializable
data class Document(
    val title: String,
    @Contextual  // 运行时确定序列化策略
    val createdAt: Date,
    @Contextual
    val metadata: Any,  // 可以是任意类型
)

3.2 序列化器模块

通过 SerializersModule 配置运行时序列化策略:

kotlin 复制代码
// 创建序列化器模块
val dateModule = SerializersModule {
    contextual(Date::class, DateAsLongSerializer)
    contextual(Any::class) { args -> 
        // 根据实际类型选择序列化器
        when (args[0]) {
            String::class -> String.serializer()
            Int::class -> Int.serializer()
            else -> error("Unsupported type")
        }
    }
}

// 配置 Json 格式
val json = Json {
    serializersModule = dateModule
}

// 使用
val document = Document("Report", Date(), "Important")
val jsonString = json.encodeToString(document)

3.3 多序列化器并存

使用 @KeepGeneratedSerializer 保留插件生成的序列化器:

kotlin 复制代码
@KeepGeneratedSerializer  // 保留插件生成的序列化器
@Serializable(with = ColorAsStringSerializer::class)  // 默认使用自定义序列化器
data class Color(val rgb: Int)

// 使用不同的序列化器
val green = Color(0x00ff00)

// 使用自定义序列化器(默认)
val customJson = Json.encodeToString(green)  // "00ff00"

// 使用插件生成的序列化器
val generatedJson = Json.encodeToString(Color.generatedSerializer(), green)  // {"rgb":65280}

四、序列化器配置优先级

当多种配置方式同时存在时,优先级从高到低为:

手动传递 > 属性注解 > 文件级配置 > 别名绑定 > 上下文配置 > 类注解 > 自动配置

别名绑定额外说明:

  • 别名属性上添加额外注解或文件级配置的序列化器优先级更高
  • 额外添加的序列化器遵循优先级规则
kotlin 复制代码
typealias AliasDate = @Serializable(AliasSerializer::class) Date

@Serializable
data class TestEvent(
    @Contextual
    @Serializable(with = PropertySerializer::class)
    val aliasDate: AliasDate,
    // PropertySerializer > ContextualSerializer > AliasSerializer
)

下图展示了序列化器使用方式的完整分类和优先级关系:

flowchart TD C["静态绑定"] --> E["优先级 1
手动传递
最高优先级"] C --> F["优先级 2
属性注解
@Serializable(with=...)"] C --> I["优先级 3
文件级配置
@file:UseSerializers"] C --> J["优先级 4
别名绑定
typealias"] C --> G["优先级 6
类注解
@Serializable(with=...)"] D["动态绑定"] --> K["优先级 5
上下文配置
@Contextual + Module"] B["自动配置"] --> H["优先级 7
自动配置
默认行为"] classDef root fill:#f5f5f5,stroke:#616161,stroke-width:3px,color:#424242 classDef category fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,color:#01579b classDef priority1 fill:#ffebee,stroke:#f44336,stroke-width:3px,color:#c62828 classDef priority2 fill:#fff3e0,stroke:#ff9800,stroke-width:2px,color:#ef6c00 classDef priority3 fill:#fff8e1,stroke:#ffc107,stroke-width:2px,color:#f57f17 classDef priority4 fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px,color:#7b1fa2 classDef priority5 fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px,color:#303f9f classDef priority6 fill:#e3f2fd,stroke:#2196f3,stroke-width:2px,color:#1565c0 classDef priority7 fill:#e8f5e8,stroke:#4caf50,stroke-width:2px,color:#2e7d32 class A root class B,C,D category class E priority1 class F priority2 class I priority3 class J priority4 class K priority5 class G priority6 class H priority7

五、第三方类序列化

在实际项目中,经常需要序列化第三方库的类(如 java.util.Datejava.time.LocalDateTime 等)。由于这些类没有 @Serializable 注解,需要特殊处理。

5.1 直接序列化第三方类

当需要直接序列化第三方类时,需要创建序列化器并手动指定,可选方式:

  • 手动传递序列化器 :为第三方类创建自定义序列化器,通过 Json.encodeToString(serializer, obj) 直接传递
  • 集合序列化器 :使用 ListSerializerMapSerializer 等包装第三方类序列化器处理集合

5.2 第三方类作为属性间接引用

当第三方类作为自定义类的属性时,可选处理方式:

  • 属性级别绑定 :使用 @Serializable(with = CustomSerializer::class) 为特定属性指定序列化器
  • 文件级别配置 :使用 @file:UseSerializers(CustomSerializer::class) 为整个文件统一配置
  • 上下文配置 :使用 @Contextual 注解配合 SerializersModule 进行运行时配置
  • 泛型参数绑定 :为泛型类型参数指定序列化器,如 List<@Serializable(CustomSerializer::class) ThirdPartyClass>

六、最佳实践

6.1 选择合适的绑定方式

  • 自动配置:首选方式,适用于基本类型和标准库类型
  • 手动传递:临时场景、测试场景、直接序列化第三方类
  • 属性注解:特定字段需要特殊处理、第三方类属性
  • 类注解:整个类需要统一的序列化策略
  • 文件级配置:模块级别统一配置、多个类使用同一第三方类
  • 上下文配置:需要运行时动态选择,适用特定业务定制 Json、第三方类动态配置

6.2 性能考虑

手写序列化器推荐使用 object,避免序列化过程中重复创建:

kotlin 复制代码
// 推荐:使用 object 单例
object DateSerializer : KSerializer<Date> { /* ... */ }

// 不推荐:每次创建新实例
class DateSerializer : KSerializer<Date> { /* ... */ }

6.3 错误处理策略

序列化器的错误处理应该根据业务需求选择合适的策略:

策略1:不处理异常(推荐)

默认情况下应该选择不处理异常,让异常向上传播,由业务层决定如何处理。

策略2:默认值兜底异常

仅在特定场景下使用,需要注意这会让业务逻辑污染数据层的逻辑:

kotlin 复制代码
object SafeDateSerializer : KSerializer<Date> {
    override val descriptor = PrimitiveSerialDescriptor("SafeDate", PrimitiveKind.LONG)
    
    override fun serialize(encoder: Encoder, value: Date) {
        encoder.encodeLong(value.time)
    }
    
    override fun deserialize(decoder: Decoder): Date {
        return try {
            Date(decoder.decodeLong())
        } catch (e: Exception) {
            Date(0)  // 默认值兜底,但这是业务逻辑的决策
        }
    }
}

选择建议:

  • 策略1:适用于大多数场景,保持数据层的纯净性,异常处理交给业务层
  • 策略2:仅在确实需要容错且有明确默认值语义的场景下使用

结语

掌握这些使用技巧,能够让您在各种复杂的业务场景中都能游刃有余地使用 Kotlin Serialization,构建出既高效又优雅的序列化解决方案。

相关推荐
FunnySaltyFish2 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Kapaseker8 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z3 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton4 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream4 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam4 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker4 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
糖猫猫cc5 天前
Kite:两种方式实现动态表名
java·kotlin·orm·kite
如此风景5 天前
kotlin协程学习小计
android·kotlin