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

Kotlin序列化如何将Map<String,*>的可序列化属性展开到父JSON对象

编译错误说明

你遇到的编译报错翻译如下:

org.jetbrains.kotlin.codegen.编译异常:后端(JVM)内部错误:未找到Any?类型元素的序列化器

该错误触发原因:你定义的Map<String, *>星投影后值类型为Any?,kotlinx.serialization默认无法为无明确类型信息的Any类型自动查找序列化器,IntelliJ无报错是IDE静态检查的遗漏,实际编译阶段序列化框架生成字节码时就会触发该校验。
另外你原有自定义序列化器的逻辑也存在错误:Encoder的编码是有结构规范的,不能直接连续调用encodeString输出字段,这样无法生成合法的JSON对象结构。


解决方案

密封类包裹属性值不是唯一解决方案,你可以根据场景选择以下两种实现:

方案1:直接基于JSON结构实现自定义序列化(推荐,无需额外定义类)

利用kotlinx.serialization 1.4+提供的实验性API,直接合并固定字段和动态属性生成根JSON对象,代码量极少,完全实现你要的类似@JsonAnyGetter的展开效果:

import kotlinx.serialization.*
import kotlinx.serialization.json.*

fun main() {
    val json = Json {
        prettyPrint = true
        // 启用实验性API支持
        @OptIn(ExperimentalSerializationApi::class)
        useAlternativeNames = false
    }
    val falcon = Entity("0", "falcon", mapOf("max-flight-range-nm" to 10, "name" to "falcon-9"))
    println(json.encodeToString(falcon))
    // 输出效果:
    // {
    //     "id": "0",
    //     "type": "falcon",
    //     "max-flight-range-nm": 10,
    //     "name": "falcon-9"
    // }
}

@Serializable(with = Entity.Serializer::class)
class Entity(val id: String, val type: String, val properties: Map<String, *>) {
    @OptIn(ExperimentalSerializationApi::class)
    object Serializer : KSerializer<Entity> {
        override val descriptor = buildClassSerialDescriptor("Entity")

        override fun serialize(encoder: Encoder, value: Entity) {
            require(encoder is JsonEncoder) { "该序列化器仅支持JSON格式输出" }
            // 合并固定字段和动态属性到同一个JSON对象
            val allFields = buildMap<String, JsonElement> {
                put("id", Json.encodeToJsonElement(value.id))
                put("type", Json.encodeToJsonElement(value.type))
                value.properties.forEach { (k, v) ->
                    put(k, Json.encodeToJsonElement(v))
                }
            }
            encoder.encodeJsonElement(JsonObject(allFields))
        }
    }
}

该方案只要你确保properties中的值都是Kotlin基础类型、String、或者标注了@Serializable的类,就可以正常运行,不需要额外定义其他类。

方案2:密封类包裹属性值(适合强类型校验场景)

如果你不想使用实验性API,或者需要编译期校验属性值类型合法性,可以选择密封类方案,这是更稳妥的兼容方案,但不是唯一选择:

@Serializable
sealed class PropertyValue {
    @Serializable
    data class IntValue(val value: Int) : PropertyValue()
    @Serializable
    data class StringValue(val value: String) : PropertyValue()
    @Serializable
    data class CustomClassValue(val value: YourSerializableClass) : PropertyValue()
    // 按需扩展其他类型
}

// 对应的Entity类修改properties类型
@Serializable
class Entity(val id: String, val type: String, val properties: Map<String, PropertyValue>)

序列化时只需取出密封类的value字段序列化即可,编译期就能检查属性值类型是否合法,不会出现运行时序列化失败的问题。


内容的提问来源于stack exchange,提问作者Jason

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 13:24:04