如何在 Kotlinx 序列化中序列化“任何”类型?

Posted

技术标签:

【中文标题】如何在 Kotlinx 序列化中序列化“任何”类型?【英文标题】:How to serialize "Any" type in Kotlinx Serialization? 【发布时间】:2021-05-14 19:18:27 【问题描述】:

我有一个针对网络流量进行序列化的类。

@Serializable    
data class Packet(val dataType: String, val payload: Any)

我使用 Java 序列化通过网络发送它。接收方不知道有效载荷的类型,但 Java 可以很好地反序列化它,然后我使用 when(dataType) 作为查找正确地将 Any 对象转换为正确的类型。轻松自在。

但 Kotlinx 序列化(使用 ProtoBuf)对这种 Any 类型持保留态度,原因对我来说并不明显。我无法为Any 注册序列化程序。在文档中,他们推荐了一种多态方法,这种方法可以工作,但您必须输入数据包:

data class Packet<out T : Any>(val dataType: String, val payload: T) : SomeBaseClass<T>

但这有点糟糕,因为它使用内联具体化类型来压低很多代码路径,而且这并不能解决接收端不知道尝试反序列化有效负载的类型,因为没有查看dataType 字段优先。

这是最糟糕的 catch-22。框架不会忽略 payload: Any 字段(给出编译错误),我什至不能编写自定义序列化程序,因为在客户序列化程序(对于描述符)中定义类型为 Anyelement 给出相同的结果“没有为Any注册序列化程序”的运行时错误。

【问题讨论】:

【参考方案1】:

我使用 Java 序列化通过网络发送它。接收方不知道有效负载的类型,但 Java 可以很好地反序列化它,然后我使用 when(dataType) 作为查找来正确地将 Any 对象转换为其正确的类型。轻松自在。

这是因为 java 序列化是相当原始的 - 只有一种方法可以序列化(并因此反序列化)对象。在 kotlinx.serialization 中,每个类都可以有自己的序列化策略(甚至多个)。这种灵活性是有代价的。 可以处理Any 的序列化(对于声明的子类列表),但是在一般情况下,基于部分反序列化对象的dataType 字段的反序列化策略的动态确定是不可能的,因为不能保证dataType 字段将先反序列化。一些序列化格式(如 JSON 或 Protobuf)具有无序架构。 payload 可能会在dataType 之前被反序列化,而Decoder 接口不允许返回/进行多次传递。

如果您确定序列化格式/消息中的属性顺序(或者只是觉得幸运),您可以使用以下自定义序列化程序:

import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*

@Serializable(with = PacketSerializer::class)
data class Packet(val dataType: String, val payload: Any)

object PacketSerializer : KSerializer<Packet> 
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Packet") 
        element("dataType", serialDescriptor<String>())
        element("payload", buildClassSerialDescriptor("Any"))
    

    @Suppress("UNCHECKED_CAST")
    private val dataTypeSerializers: Map<String, KSerializer<Any>> =
        mapOf(
            "String" to serializer<String>(),
            "Int" to serializer<Int>(),
            //list them all
        ).mapValues  (_, v) -> v as KSerializer<Any> 

    private fun getPayloadSerializer(dataType: String): KSerializer<Any> = dataTypeSerializers[dataType]
        ?: throw SerializationException("Serializer for class $dataType is not registered in PacketSerializer")

    override fun serialize(encoder: Encoder, value: Packet) 
        encoder.encodeStructure(descriptor) 
            encodeStringElement(descriptor, 0, value.dataType)
            encodeSerializableElement(descriptor, 1, getPayloadSerializer(value.dataType), value.payload)
        
    

    @ExperimentalSerializationApi
    override fun deserialize(decoder: Decoder): Packet = decoder.decodeStructure(descriptor) 
        if (decodeSequentially()) 
            val dataType = decodeStringElement(descriptor, 0)
            val payload = decodeSerializableElement(descriptor, 1, getPayloadSerializer(dataType))
            Packet(dataType, payload)
         else 
            require(decodeElementIndex(descriptor) == 0)  "dataType field should precede payload field" 
            val dataType = decodeStringElement(descriptor, 0)
            val payload = when (val index = decodeElementIndex(descriptor)) 
                1 -> decodeSerializableElement(descriptor, 1, getPayloadSerializer(dataType))
                CompositeDecoder.DECODE_DONE -> throw SerializationException("payload field is missing")
                else -> error("Unexpected index: $index")
            
            Packet(dataType, payload)
        
    


【讨论】:

你说得对,我并没有真正考虑过有序与无序的事情。很好的答案和代码!

以上是关于如何在 Kotlinx 序列化中序列化“任何”类型?的主要内容,如果未能解决你的问题,请参考以下文章

Kotlinx 序列化:如何绕过 reified typeargs 进行反序列化?

@SerialInfo - 如何使用 Kotlinx 序列化管理用户定义的串行注释?

使用 kotlinx.serialization 库反序列化具有不同值类型的 JSON 数组

如何使用 kotlinx.serialization 在 Ktor 中序列化 Web Socket Frame.text

在 kotlinx.serialization 中编码/解码 JSON“字符串”

如何禁用 kotlinx 序列化多态鉴别器?