将 JSON 反序列化为具有通用字段的 Serializable 类 - 错误:不允许类型参数中的星形投影
Posted
技术标签:
【中文标题】将 JSON 反序列化为具有通用字段的 Serializable 类 - 错误:不允许类型参数中的星形投影【英文标题】:Deserializing JSON into Serializable class with generic field - error: Star projections in type arguments are not allowed 【发布时间】:2022-01-07 23:02:20 【问题描述】:简介
我在两个使用不同语言的后端服务器之间发送 JSON 消息。生产的 服务器创建各种 JSON 消息,用元数据包装在消息中。
包装类是Message
,消费服务器必须确定它的消息类型
仅根据消息内容接收。
当我尝试使用 star-projection 来 反序列化消息,我得到一个错误。
示例
import kotlinx.serialization.json.Json
@Language("JSON")
val carJson = """
"message_type": "some message",
"data":
"info_type": "Car",
"name": "Toyota"
""".trimIndent()
// normally I wouldn't know what the Json message would be - so the type is Message<*>
val actualCarMessage = Json.decodeFromString<Message<*>>(carJson)
错误信息
Exception in thread "main" java.lang.IllegalArgumentException: Star projections in type arguments are not allowed, but Message<*>
at kotlinx.serialization.SerializersKt__SerializersKt.serializerByKTypeImpl$SerializersKt__SerializersKt(Serializers.kt:81)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:59)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
at ExampleKt.main(example.kt:96)
at ExampleKt.main(example.kt)
类结构
我想将 JSON 反序列化为一个数据类 Message
,它有一个泛型类型的字段。
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Message<out DataType : SpecificInformation>(
@SerialName("message_type")
val type: String,
@SerialName("data")
val data: DataType,
)
该字段受密封接口SpecificInformation
的约束,并具有一些实现。
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonClassDiscriminator
@JsonClassDiscriminator("info_type")
sealed interface SpecificInformation
@SerialName("info_type")
val infoType: String
@Serializable
@SerialName("User")
data class UserInformation(
@SerialName("info_type")
override val infoType: String,
val name: String,
) : SpecificInformation
// there are more implementations...
解决方法?
这是一个已知的 问题(kotlinx.serialization/issues/944) , 所以我正在寻找解决方法。
我可以控制 JSON 结构和库 - 尽管我更喜欢 kotlinx.序列化。
我不能改变有两个 JSON 对象,一个在另一个里面,而鉴别器是 在内部类中。
自定义序列化器会很棒。但我更愿意在类或文件上配置这个
(使用 @Serializable(with = ...)
或 @file:UseSerializers(...)
)作为使用
自定义 SerializersModule
不是无缝的。
尝试:JsonContentPolymorphicSerializer
我编写了一个自定义序列化程序,只有在它被专门使用时才会使用它(这是我想要的
避免)。它也很笨重,如果数据类发生变化或添加新的类会中断,并且
不会从sealed interface
中受益。
这个可以改进吗
-
可以通用吗?
Json.decodeFromString<Message<*>>(carJson)
它没有任何硬编码字符串?
class MessageCustomSerializer : JsonContentPolymorphicSerializer<Message<*>>(Message::class)
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out Message<*>>
val discriminator = element
.jsonObject["data"]
?.jsonObject?.get("info_type")
?.jsonPrimitive?.contentOrNull
println("found discriminator $discriminator")
val subclassSerializer = when (discriminator?.lowercase())
"user" -> UserInformation.serializer()
"car" -> CarInformation.serializer()
else -> throw IllegalStateException("could not find serializer for $discriminator")
println("found subclassSerializer $subclassSerializer")
return Message.serializer(subclassSerializer)
fun main()
@Language("JSON")
val carJson = """
"message_type": "another message",
"data":
"info_type": "Car",
"brand": "Toyota"
""".trimIndent()
val actualCarMessage =
Json.decodeFromString(MessageCustomSerializer(), carJson)
val expectedCarMessage = Message("another message", CarInformation("Car", "Toyota"))
require(actualCarMessage == expectedCarMessage)
println("car json parsing ❌")
println("car json parsing ✅")
@Serializable(with = ...
- 无限循环
我尝试将MessageCustomSerializer
直接应用到Message
...
@Serializable(with = MessageCustomSerializer::class)
data class Message<out T : SpecificInformation>(
//...
但后来我无法访问插件生成的序列化程序,这会导致无限循环。
return Message.serializer(subclassSerializer) // calls 'MessageCustomSerializer', causes infinite loop
@Serializer(forClass = ...)
- 不是通用的
除了用@Serializable(with = MessageCustomSerializer::class)
注释Message
,我
试过了
导出a plugin-generated serializer:
@Serializer(forClass = Message::class)
object MessagePluginGeneratedSerializer : KSerializer<Message<*>>
但是这个序列化器不是通用的,会导致错误
java.lang.AssertionError: No such value argument slot in IrConstructorCallImpl: 0 (total=0).
Symbol: MessageCustomSerializer.<init>|-5645683436151566731[0]
at org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpressionKt.throwNoSuchArgumentSlotException(IrMemberAccessExpression.kt:66)
at org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression.putValueArgument(IrFunctionAccessExpression.kt:31)
at org.jetbrains.kotlinx.serialization.compiler.backend.ir.IrBuilderExtension$DefaultImpls.irInvoke(GeneratorHelpers.kt:210)
at org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializableCompanionIrGenerator.irInvoke(SerializableCompanionIrGenerator.kt:35)
【问题讨论】:
【参考方案1】:您在这里问了很多事情,所以我将简单地尝试就您所犯的错误提供一些指示,这些错误似乎让您陷入困境。考虑到这些,并阅读我链接到的文档,我相信您应该能够自己解决其余的问题。
多态序列化
熟悉kotlinx.serialization
polymorphic serialization。当您尝试序列化 Message<*>
和 DataType
时,您正在尝试使用多态序列化。
如果您将Message<*>
序列化为根对象,则明确指定PolymorphicSerializer
(正如我在the bug report you link to 中发布的那样)应该可以工作。例如,Json.decodeFromString( PolymorphicSerializer( Message::class ), carJson )
。
附:我不能 100% 确定您在这里尝试做的事情与错误报告中的相同。无论哪种方式,明确指定序列化程序都应该有效,无论它是否是一个错误,您不应该被要求这样做。
Message
和 DataType
中的 message_type
和 info_type
字段分别为 class discriminators。您需要在 Json 设置中进行配置,并在具体类上设置正确的 SerialName
以使其正常工作。只有starting from kotlinx.serialization
1.3.0 使用@JsonClassDiscriminator
才能为每个层次结构使用不同的类鉴别器。
覆盖插件生成的序列化程序
但后来我无法访问插件生成的序列化程序,这会导致无限循环。
@Serializable(with = ...)
覆盖插件生成的序列化程序。如果要保留插件生成的序列化器,请不要申请with
。
当您直接序列化对象(作为根对象)时,您仍然可以将不同的序列化程序作为第一个参数传递给encode
/decode
。当您想要覆盖序列化程序以用于嵌套在根对象某处的特定属性时,use @Serializable
on the property。
多态性和泛型类
"No such value argument slot in IrConstructorCallImpl: 0" 错误是意料之中的。
如果您想为多态泛型类指定序列化程序,请need to do more work。
【讨论】:
以上是关于将 JSON 反序列化为具有通用字段的 Serializable 类 - 错误:不允许类型参数中的星形投影的主要内容,如果未能解决你的问题,请参考以下文章
使用spring boot将JSON反序列化为带有通用对象列表的POJO