02.Kotlin Serialization 属性序列化控制

前言

在 Kotlin Serialization 中,序列化规则决定了类属性的序列化行为。本文将详细介绍属性序列化控制、名称定制、默认值处理和类型安全等核心规则,帮助开发者准确理解数据的序列化过程。

1. 属性序列化控制

1.1 背景字段序列化规则

在 Kotlin Serialization 中,只有具有背景字段(backing field)的属性才会被序列化。

什么是背景字段?

背景字段是 Kotlin 为属性自动生成的内部存储字段。当属性需要存储值时,编译器会创建一个背景字段。具体规则如下:

  • 构造函数参数声明的属性(val/var)有背景字段
  • 类体中声明的可变属性(var)有背景字段
  • 只有自定义 getter 的属性没有背景字段
  • 委托属性没有背景字段(值存储在委托对象中)
kotlin 复制代码
@Serializable
class Student(val name: String) {
    var id: Int = 0
    val domainName: String
        get() = "Student/$name"
    val delegateName by ::name
}

fun main() {
    val data = Student("iKun").apply { id = 10001 }
    println(Json.encodeToString(data))
    // 输出: {"name":"iKun","id":10001}
}

从输出结果可以看出,只有 nameid 属性出现在 JSON 中,而 domainNamedelegateName 属性被忽略了。

1.2 默认值和可选属性

1.2.1 可选属性

当对象的某个属性在输入数据中缺失时,反序列化会失败。解决方法是为属性提供默认值,使该属性自动变为可选。

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

fun main() {
    val json1 = """{"name":"iKun"}"""
    println(Json.decodeFromString<Student>(json1))
    // 输出: Student(name=iKun, language=Chinese)

    val json2 = """{"name":"iKun","language":"English"}"""
    println(Json.decodeFromString<Student>(json2))
    // 输出: Student(name=iKun, language=English)
}

1.2.2 默认值编码策略

默认情况下,等于默认值的属性不会被编码到 JSON 中,这样可以减少数据冗余。

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

fun main() {
    val student1 = Student(name = "iKun")
    println(Json.encodeToString(student1))
    // 输出: {"name":"iKun"}

    val student2 = Student(name = "iKun", language = "English")
    println(Json.encodeToString(student2))
    // 输出: {"name":"iKun","language":"English"}
}

1.2.3 强制编码默认值 - @EncodeDefault

使用 @EncodeDefault 注解可以强制编码默认值,无论格式设置如何。该注解只影响编码过程,不影响解码。

kotlin 复制代码
@Serializable
data class Student(
    val name: String,
    @EncodeDefault
    val language: String = "Chinese"
)

fun main() {
    println(Json.encodeToString(Student(name = "iKun")))
    // 输出: {"name":"iKun","language":"Chinese"}
}

@EncodeDefault 注解可以指定参数 EncodeDefault.Mode.NEVEREncodeDefault.Mode.ALWAYS 强制指定默认值的编码行为。

1.2.4 Json 格式的 encodeDefaults 配置

除了使用 @EncodeDefault 注解控制单个属性外,还可以通过 Json 实例的 encodeDefaults 配置来全局控制默认值的编码策略。

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

fun main() {
    val json = Json { encodeDefaults = true }
    println(json.encodeToString(Student(name = "iKun")))
    // 输出: {"name":"iKun","language":"Chinese"}
}

注意事项:

  • @EncodeDefault 注解的优先级高于 encodeDefaults 配置
  • 这个配置只影响编码过程,不影响解码过程

1.2.5 初始化器调用优化

当可选属性在输入中存在时,对应的初始化器不会被调用,这是为了性能考虑。

kotlin 复制代码
fun default(): String {
    println("获取默认语言.")
    return "Chinese"
}

@Serializable
data class Student(
    val name: String,
    val language: String = default(),
)

fun main() {
    Json.decodeFromString<Student>("""{"name":"iKun","language":"English"}""")
    // 不会打印"获取默认语言."
}

1.3 瞬态属性 - @Transient

使用 @Transient 注解可以将属性从序列化过程中排除。瞬态属性必须提供默认值。

kotlin 复制代码
@Serializable
data class Student(
    val name: String,
    @Transient
    val language: String = "Chinese",
)

fun main() {
    println(Json.encodeToString(Student(name = "iKun")))
    // 输出: {"name":"iKun"}

    println(Json.encodeToString(Student(name = "iKun", language = "English")))
    // 输出: {"name":"iKun"}
}

1.4 必需属性 - @Required

对于有默认值的属性,可以使用 @Required 注解强制要求该属性在序列化格式中必须存在。

kotlin 复制代码
@Serializable
data class Student(
    val name: String,
    @Required
    val language: String = "Chinese",
)

fun main() {
    Json.decodeFromString<Student>("""{"name":"iKun"}""")
    // 运行报错:Caused by: kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'org.demo.Student', but it was missing
}

2. 序列化名称定制

2.1 自定义字段名称 - @SerialName

默认情况下,序列化使用的字段名称与源代码中的属性名称相同。可以使用 @SerialName 注解来自定义序列化名称。

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

fun main() {
    val jsonStr = Json.encodeToString(Student(name = "iKun", language = "Chinese"))
    println(jsonStr)
    // 输出: {"studentName":"iKun","language":"Chinese"}

    println(Json.decodeFromString<Student>(jsonStr))
    // 输出: Student(name=iKun, language=Chinese)
}

2.2 枚举类型的序列化

2.2.1 枚举默认序列化

枚举类型天然支持序列化,无需添加 @Serializable 注解。默认情况下,枚举值以字符串形式序列化,使用枚举条目名称作为序列化值。

kotlin 复制代码
enum class Language {
    KOTLIN, PYTHON
}

@Serializable
data class Student(
    val name: String,
    val favoriteLanguage: Language
)

fun main() {
    val student = Student("iKun", Language.KOTLIN)
    println(Json.encodeToString(student))
    // 输出: {"name":"iKun","favoriteLanguage":"KOTLIN"}
    
    val json = """{"name":"iKun","favoriteLanguage":"PYTHON"}"""
    println(Json.decodeFromString<Student>(json))
    // 输出: Student(name=iKun, favoriteLanguage=PYTHON)
}

2.2.2 自定义枚举序列名称

枚举类型也可以使用 @SerialName 自定义序列化名称。使用 @SerialName 的枚举类必须标记为 @Serializable

kotlin 复制代码
@Serializable
enum class Language {
    @SerialName("1") KOTLIN,
    @SerialName("2") PYTHON,
}

@Serializable
data class Student(
    val name: String,
    val favoriteLanguage: Language
)

fun main() {
    val student = Student("iKun", Language.KOTLIN)
    println(Json.encodeToString(student))
    // 输出: {"name":"iKun","favoriteLanguage":"1"}

    val json = """{"name":"iKun","favoriteLanguage":"2"}"""
    println(Json.decodeFromString<Student>(json))
    // 输出: Student(name=iKun, favoriteLanguage=PYTHON)
}

3. 类型安全和数据验证

3.1 构造时数据验证

反序列化过程会调用所有 init 块,因此可以在其中进行数据验证。

kotlin 复制代码
@Serializable
data class Student(
    val name: String,
    val language: String
) {
    init {
        require(name.isNotEmpty() && name.isNotBlank()) { "名称不合法" }
    }
}

fun main() {
    val json = """{"name":"","language":"Chinese"}"""
    Json.decodeFromString<Student>(json)
    // 运行报错: java.lang.IllegalArgumentException: 名称不合法 at org.example.Student.<init>
}

3.2 类型安全保证

Kotlin Serialization 严格执行类型安全,不允许将 null 值赋给非空类型的属性。

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

fun main() {
    val json = """{"name":"","language":null}"""
    Json.decodeFromString<Student>(json)
    // 运行报错: kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 22: Expected string literal but 'null' literal was found
}

3.3 可空属性处理

可空属性会被正常序列化和反序列化:

kotlin 复制代码
@Serializable
class Project(
    val name: String, 
    val renamedTo: String? = null // 可空属性
)

fun main() {
    val data = Project("kotlinx.serialization")
    println(Json.encodeToString(data))
    // 输出: {"name":"kotlinx.serialization"} (null 值不被编码)
    
    val dataWithRename = Project("old-name", "new-name")
    println(Json.encodeToString(dataWithRename))
    // 输出: {"name":"old-name","renamedTo":"new-name"}
}

3.4 构造函数属性要求

@Serializable 注解要求主构造函数的所有参数都必须是属性。如需其他逻辑,可以使用私有主构造函数配合辅助构造函数:

kotlin 复制代码
@Serializable
class Student private constructor(
    val name: String,
    val language: String,
) {
    constructor(input: String) : this(
        name = input.substringBefore('/'),
        language = input.substringBefore('/'),
    )
}

fun main() {
    val json = Json { encodeDefaults = true }
    println(json.encodeToString(Student(input = "iKun/Chinese")))
    // 输出: {"name":"iKun","language":"iKun"}
}

4. 总结与最佳实践

4.1 核心规则回顾

控制类型 实现方式 关键要点
属性选择 背景字段机制 委托属性、计算属性不参与序列化
属性排除 @Transient 必须提供默认值
强制必需 @Required 覆盖默认值的可选性
名称定制 @SerialName 枚举使用时需添加@Serializable
默认值控制 @EncodeDefault / encodeDefaults 注解优先级高于全局配置

4.2 设计限制与应用场景

Kotlin Serialization 专为纯数据序列化设计,具有以下限制:

  • 不支持复杂对象图和循环引用
  • 相同对象的多次引用会被分别序列化
  • 适用于简单的数据传输对象(DTO)

4.3 开发建议

避免常见陷阱

  • 注意委托属性不会被序列化
  • 避免在属性初始化器中依赖副作用
  • 主构造函数参数必须是属性

性能优化

  • 使用@Transient排除不必要的数据
  • 合理配置默认值策略减少传输量
  • 根据场景选择不同的Json配置

掌握这些规则,能够充分发挥 Kotlin Serialization 的优势,构建高效可靠的数据序列化系统。

相关推荐
FunnySaltyFish7 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Kapaseker13 小时前
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
Kapaseker5 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
糖猫猫cc5 天前
Kite:两种方式实现动态表名
java·kotlin·orm·kite
如此风景5 天前
kotlin协程学习小计
android·kotlin