You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中使用kotlinx.serialization与Charleskorn的YAML插件时,如何让非多态类的@SerialName在序列化输出中生效?

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 07:34:16