Kotlin Serializer 基础使用
Kotlin Serializer 是 KMP(Kotlin多平台)的一个组件,可以应用于 JVM、Native、Wasm、Js 等场景。
最新版本及使用文档参考 Github
依赖
需要引入 plugins 以及 dependencies,其中不同场景引入的 plugins 是相同的,而 dependencies 则根据需求引入 json
或 core
这里以 JVM 平台测试,不引入其他 KMP 库
plugins {
kotlin("jvm") version "2.0.0"
kotlin("plugin.serialization") version "2.0.20"
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
testImplementation(kotlin("test"))
}
测试对象
创建几个测试对象,用于后文的测试代码
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
// 这是序列化注解,标记后在编译期将会由编译器生成对应的代码
@Serializable
data class Project(
val name: String,
val language: String
)
序列化与反序列化
序列化
fun main() {
val data = Project("序列化测试", "Kotlin")
println(Json.encodeToString(data))
}
{"name":"序列化测试","language":"Kotlin"}
反序列化
fun main() {
val jsonString = """
{"name":"序列化测试","language":"Kotlin"}
""".trimIndent()
val data = Json.decodeFromString<Project>(jsonString)
println(data)
}
Project(name=序列化测试, language=Kotlin)
Json 配置项
不同于其他 Java 第三方 Json 转换库,Kotlin Serializer 默认的 Json 配置十分简陋,而 Kotlin 又有严格的空值校验,这代表这大多数情况下使用默认配置在运行时可能出现异常:
异常场景
空值产生的异常
将一个字段改为可空,并修改 Json 字符串进行反序列化:
@Serializable
data class Project(
val name: String,
val language: String?
)
fun main() {
val jsonString = """
{"name":"序列化测试"}
""".trimIndent()
val data = Json.decodeFromString<Project>(jsonString)
println(data)
}
Exception in thread "main" kotlinx.serialization.MissingFieldException: Field 'language' is required for type with serial name 'cn.cimoc.serializer.Project', but it was m
issing at path: $
按照一般的逻辑,我们当然希望这种情况下框架能在反序列化时自动填充 null 值,然而 Kotlin Serializer 的默认策略会先进行检测并抛出异常。
若不对框架进行额外配置,一种解决方案是为 language参数设置默认值:
@Serializable
data class Project(
val name: String,
val language: String? = null
)
额外的字段产生的异常
实际应用中,前端传入的参数是不可控的,当参数出现未定义的字段时,Kotlin Serializer 在默认配置下会抛出异常。
去除 Project
类的一个字段,并重新测试:
@Serializable
data class Project(
val name: String
)
fun main() {
val jsonString = """
{"name":"序列化测试","language":"Kotlin"}
""".trimIndent()
val data = Json.decodeFromString<Project>(jsonString)
println(data)
}
Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 17: Encountered an unknown key 'language' at path: $
.name
Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys.
JSON input: {"name":"序列化测试","language":"Kotlin"}
配置项
格式化输出
val format = Json { prettyPrint = true }
fun main() {
val data = Project("序列化测试", "Kotlin")
println(format.encodeToString(data))
}
{
"name": "序列化测试",
"language": "Kotlin"
}
宽松校验
通过此项配置,在反序列化时,字符串可以不用严格按照 Json 的规范,例如键值对可以省略引号。
val format = Json { isLenient = true }
fun main() {
val jsonString = """
{
name: 序列化测试
language: Kotlin
}
""".trimIndent()
val data = format.decodeFromString<Project>(jsonString)
println(data)
}
Project(name=序列化测试, language=Kotlin)
忽略未知 Key
val format = Json { ignoreUnknownKeys = true }
@Serializable
data class Project(val name: String)
fun main() {
val data = format.decodeFromString<Project>("""
{"name":"序列化测试","language":"Kotlin"}
""")
println(data)
}
Project(name=序列化测试)
空值填充
@Serializable
data class Project(
val name: String,
val language: String?,
)
val format = Json { explicitNulls = true }
fun main() {
val jsonString = """
{
"name": "序列化测试"
}
""".trimIndent()
val data = format.decodeFromString<Project>(jsonString)
println(data)
}
Project(name=序列化测试, language=null)
字段重命名
重命名并不是一个配置项,但作为序列化中不可或缺的特性,也一并放在这里。
@Serializable
data class Project(
@SerialName("rename")
val name: String,
val language: String?,
)
fun main() {
val data = Json.encodeToString(Project("字段重命名", null))
println(data)
}
{"rename":"字段重命名"}
更多
更多配置项参考 官方文档
JsonElement
JsonElement 有点像 FastJson 中的 JSONObject,都提供了近似原生操控 Json 的功能。
不同的是,JsonElement 的基本类型是 JsonPrimitive
,它可以转换为 Kotlin 基本类型,或 JsonArray
、JsonObject
;而 FastJson 中基本类型是 JSONObject
。它们俩的功能基本相同,但 JsonPrimitive
更强调语义,将大括号{}(JsonObject
)、中括号[](JsonArray
)、其他(基本类型)都拆分开来。
自定义序列化
不同于其他主流序列化框架,Kotlin Serializer 只支持 Kotlin 基本类型,因此 Java 中的 Date、LocalDateTime 等类型无法通过序列化编译,你将会得到一条红色的波浪线。更多的时候,你需要自己动手。
KSerializer
创建一个自定义的序列化器,只需要实现 KSerializer 接口
object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
private val _formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: LocalDateTime) {
encoder.encodeString(value.format(_formatter))
}
override fun deserialize(decoder: Decoder): LocalDateTime {
val str = decoder.decodeString()
return LocalDateTime.parse(str, _formatter)
}
}
@Serializable
data class Project(
val name: String,
@Serializable(with = LocalDateTimeSerializer::class)
val date: LocalDateTime,
)
fun main() {
val data = format.encodeToString(Project("测试项目", LocalDateTime.now()))
println(data)
}
{"name":"测试项目","date":"2024-10-11 17:48:07"}
使用范围
通过注解指定序列化器,可以加在类上、属性上,也可以 @file:
加在整个文件上。
但是目前为止还没在官方文档找到全局指定的功能。