Kotlin Serializer 基础使用


Kotlin Serializer 基础使用

Kotlin SerializerKMP(Kotlin多平台)的一个组件,可以应用于 JVM、Native、Wasm、Js 等场景。

最新版本及使用文档参考 Github

依赖

需要引入 plugins 以及 dependencies,其中不同场景引入的 plugins 是相同的,而 dependencies 则根据需求引入 jsoncore

这里以 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 基本类型,或 JsonArrayJsonObject;而 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: 加在整个文件上。

但是目前为止还没在官方文档找到全局指定的功能。

参考文档


文章作者: ❤纱雾
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ❤纱雾 !
评论
  目录