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,构建出既高效又优雅的序列化解决方案。

相关推荐
小孔龙7 小时前
05.Kotlin Serialization - 多态序列化入门
kotlin·json
XDMrWu7 小时前
Compose 智能重组:编译器视角下的黑科技
android·kotlin
前行的小黑炭11 小时前
Android :Compose如何监听生命周期?NavHostController和我们传统的Activity的任务栈有什么不同?
android·kotlin·app
前行的小黑炭21 小时前
Android 关于状态栏的内容:开启沉浸式页面内容被状态栏遮盖;状态栏暗亮色设置;
android·kotlin·app
用户091 天前
深入了解 Android 16KB内存页面
android·kotlin
小孔龙1 天前
03.Kotlin Serialization - 认识序列化器
kotlin·json
前行的小黑炭1 天前
【Android】 Context使用不当,存在内存泄漏,语言不生效等等
android·kotlin·app
前行的小黑炭2 天前
【Android】CoordinatorLayout详解;实现一个交互动画的效果(上滑隐藏,下滑出现);附例子
android·kotlin·app
卡尔特斯2 天前
Android Kotlin 项目代理配置【详细步骤(可选)】
android·java·kotlin