前言
在 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}
}
从输出结果可以看出,只有 name
和 id
属性出现在 JSON 中,而 domainName
和 delegateName
属性被忽略了。
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.NEVER
或 EncodeDefault.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 的优势,构建高效可靠的数据序列化系统。