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
相关产品推荐
相关产品推荐

