Kotlin中使用kotlinx.serialization与Charleskorn的YAML插件时,如何让非多态类的@SerialName在序列化输出中生效?
我太懂你的纠结了——本来想给单类加个版本化的@SerialName当YAML标签,结果直接序列化实现类时完全不显示,只有转成密封接口类型才出来,可你根本不需要多实现,这操作就很别扭。
先给你把底层逻辑说透:kotlinx.serialization的@SerialName在非多态场景下,本来就不是用来做类型标签的——它主要是用来指定序列化时的字段名,或者在多态场景下当类型鉴别符用。而Charleskorn的YAML插件严格遵循这个逻辑:只有当你序列化的是多态类型(比如密封类/接口的子类)时,才会把@SerialName作为YAML的!Tag输出,因为这时候需要区分不同的子类实现。如果直接序列化具体类,序列化器觉得不需要类型鉴别,自然就不会输出这个标签。
接下来给你几个不用被迫搞多态结构也能解决的方案,挑适合你的来:
方案一:自定义序列化器(最推荐,无多态依赖)
自己写个轻量的序列化器,手动给ClassA加上YAML标签,不管你传的是啥类型,都能稳定输出!ClassA_V1:
import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.YamlEncoder // 自定义序列化器,手动注入YAML标签 class ClassACustomSerializer( private val baseSerializer: KSerializer<ClassA> = ClassA.serializer() ) : KSerializer<ClassA> { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ClassA_V1") { // 复用原类的字段描述,不用重复定义 repeat(baseSerializer.descriptor.elementsCount) { index -> val element = baseSerializer.descriptor.getElementDescriptor(index) element(baseSerializer.descriptor.getElementName(index), element) } } override fun serialize(encoder: Encoder, value: ClassA) { // 仅针对YAML编码器,手动写入标签 if (encoder is YamlEncoder) { encoder.encodeYamlTag("!ClassA_V1") } // 用原序列化器处理字段逻辑 baseSerializer.serialize(encoder, value) } override fun deserialize(decoder: Decoder): ClassA { return baseSerializer.deserialize(decoder) } } // 给ClassA指定自定义序列化器 @Serializable(with = ClassACustomSerializer::class) class ClassA(val name: String) fun main() { val data = ClassA("Max") // 直接序列化ClassA类型,标签会正常输出 println(Yaml.default.encodeToString(data)) }
这个方案完全摆脱了多态依赖,代码量不大,还能精确控制标签内容,完美匹配你的版本化需求。
方案二:用扩展函数简化现有密封接口方案
如果你觉得自定义序列化器有点麻烦,也可以给你现有的密封接口方案加个小优化——写个扩展函数自动转成接口类型,不用每次手动声明变量类型:
import kotlinx.serialization.Serializable import kotlinx.serialization.SerialName import com.charleskorn.kaml.Yaml @Serializable sealed class ClassAMarker { abstract val name: String } @Serializable @SerialName("ClassA_V1") class ClassA(override val name: String) : ClassAMarker() // 扩展函数,一键转成标记接口类型 fun ClassA.asMarker(): ClassAMarker = this fun main() { val data = ClassA("Max") // 调用扩展函数,直接输出带标签的YAML println(Yaml.default.encodeToString(data.asMarker())) }
这个方案改动最小,利用现有代码只加了一行扩展函数,用起来也顺手。
方案三:手动使用PolymorphicSerializer(底层方案)
如果你想直接用kotlinx的多态序列化机制,不需要密封类,也可以手动配置SerializersModule并使用PolymorphicSerializer:
import kotlinx.serialization.Serializable import kotlinx.serialization.SerialName import kotlinx.serialization.builtins.PolymorphicSerializer import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.modules.subclass import com.charleskorn.kaml.Yaml @Serializable @SerialName("ClassA_V1") class ClassA(val name: String) // 配置多态模块,注册ClassA val customModule = SerializersModule { polymorphic(Any::class) { subclass(ClassA::class) } } // 用自定义模块创建YAML实例 val yaml = Yaml { serializersModule = customModule } fun main() { val data = ClassA("Max") // 用PolymorphicSerializer序列化ClassA println(yaml.encodeToString(PolymorphicSerializer(Any::class), data)) }
这个方案适合需要动态注册多个类的场景,但相对前两个稍微繁琐一点。
总结一下:如果完全不想碰多态结构,选方案一;如果能接受一个空的密封接口,方案二最省心;如果需要动态管理序列化类,方案三更灵活。
内容来源于stack exchange

