如何在 Kotlinx 序列化中序列化 "Any" 类型?

How to serialize "Any" type in Kotlinx Serialization?

我有一个 class 为网络流量序列化。

@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 字段。

这是最糟糕的第 22 条军规。该框架不会忽略 payload: Any 字段(给出编译错误),我什至无法编写自定义序列化程序,因为在客户序列化程序中定义了 Any 类型的 element (对于描述符)给出相同的 运行 时间错误“没有为 Any 注册序列化器”。“=22=”

I've used Java serialization to send it over the wire. The receiver can't know the type of the payload but Java deserializes it just fine, and then I use when(dataType) as a lookup to correctly cast the Any object to its correct type. Easy breazy.

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

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

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)
        }
    }
}