具有多态 kotlinx 序列化的自定义序列化程序

Posted

技术标签:

【中文标题】具有多态 kotlinx 序列化的自定义序列化程序【英文标题】:Custom serializer with polymorphic kotlinx serialization 【发布时间】:2020-02-15 13:29:21 【问题描述】:

有了kotlinx.serialization多态,我想得到

"type":"veh_t","owner":"Ivan","bodyType":"cistern","carryingCapacityInTons":5,"detachable":false

但我明白了

"type":"kotlin.collections.LinkedHashMap","owner":"Ivan","bodyType":"cistern","carryingCapacityInTons":5,"detachable":false

我使用以下型号

interface Vehicle 
    val owner: String


@Serializable
@SerialName("veh_p")
data class PassengerCar(
    override val owner: String,
    val numberOfSeats: Int
) : Vehicle

@Serializable
@SerialName("veh_t")
data class Truck(
    override val owner: String,
    val body: Body
) : Vehicle 
    @Serializable
    data class Body(
        val bodyType: String,
        val carryingCapacityInTons: Int,
        val detachable: Boolean
        //a lot of other fields
    )    

我应用以下Json

inline val VehicleJson: Json get() = Json(context = SerializersModule 
        polymorphic(Vehicle::class) 
            PassengerCar::class with PassengerCar.serializer()
            Truck::class with TruckKSerializer
        
    )

我使用序列化器 TruckKSerializer 因为服务器采用扁平结构。同时,在应用程序中我想使用一个对象 Truck.Body。对于扁平化,我根据这些类中的文档使用 JsonOutput 和 JsonInput 在 Serializator 中覆盖 fun serialize(encoder: Encoder, obj : T)fun deserialize(decoder: Decoder): T

object TruckKSerializer : KSerializer<Truck> 
    override val descriptor: SerialDescriptor = SerialClassDescImpl("Truck")

    override fun serialize(encoder: Encoder, obj: Truck) 
        val output = encoder as? JsonOutput ?: throw SerializationException("This class can be saved only by Json")
        output.encodeJson(json 
            obj::owner.name to obj.owner
            encoder.json.toJson(Truck.Body.serializer(), obj.body)
                .jsonObject.content
                .forEach  (name, value) ->
                    name to value
                
        )
    

    @ImplicitReflectionSerializer
    override fun deserialize(decoder: Decoder): Truck 
        val input = decoder as? JsonInput
            ?: throw SerializationException("This class can be loaded only by Json")
        val tree = input.decodeJson() as? JsonObject
            ?: throw SerializationException("Expected JsonObject")
        return Truck(
            tree.getPrimitive("owner").content,
            VehicleJson.fromJson<Truck.Body>(tree)
        )
    

最后,我使用stringify(serializer: SerializationStrategy&lt;T&gt;, obj: T)

VehicleJson.stringify(
    PolymorphicSerializer(Vehicle::class),
    Truck(
        owner = "Ivan",
        body = Truck.Body(
            bodyType = "cistern",
            carryingCapacityInTons = 5,
            detachable = false
        )
    )
)

我最终得到"type":"kotlin.collections.LinkedHashMap", ...,但我需要"type":"veh_t", ... 如何获得正确的类型?我想对Vehicle 使用多态性并使用 Truck.Body.serializer() 对 Body 对象进行编码以展平。

通过这种序列化,PassengerCar 类运行良好。

VehicleJson.stringify(
    PolymorphicSerializer(Vehicle::class),
    PassengerCar(
        owner = "Oleg",
        numberOfSeats = 4
    )
)

结果正确:

"type":"veh_p","owner":"Oleg","numberOfSeats":4

我认为问题在于自定义序列化程序TruckKSerializer。 我注意到如果我在覆盖的fun serialize(encoder: Encoder, obj : T) 下一个代码中使用

encoder
            .beginStructure(descriptor)
            .apply  
                //...
            
            .endStructure(descriptor)

我得到了正确的类型,但无法使用其序列化器将对象 Truck.Body 展平。

【问题讨论】:

【参考方案1】:

打开和关闭复合的正确方法 这是代码吗

val composite = encoder.beginStructure(descriptor)
// use composite instead of encoder here
composite.endStructure(descriptor)

你应该能够使用.encodeSerializable(Body.serializer(), body)序列化Body

并始终传递描述符,否则它将退回到 json 字典的 LinkedhashMap 之类的东西

【讨论】:

在这种情况下,您必须在描述符中使用fun encodeStringElement (desc: SerialDescriptor, index: Int, value: String)fun &lt;T : Any?&gt; encodeSerializableElement(desc: SerialDescriptor, index: Int, serializer: SerializationStrategy&lt;T&gt;, value: T) 或其他方式指定元素的位置。有可能避免这种情况吗?和 .encodeSerializable(Body.serializer(), body) 在 beginStructure/endStructure 中形成类似 commonFields, bodyFields 但我需要一个平面结构`commonFields, bodyFields``。

以上是关于具有多态 kotlinx 序列化的自定义序列化程序的主要内容,如果未能解决你的问题,请参考以下文章

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

kotlinx-serialization:找不到多态序列化器,因为缺少类鉴别器('null')

在 Kotlin/Native 中使用 kotlinx.serialization 进行多态反序列化

没有@Serializable 的数据类的自定义序列化程序

Kotlinx 序列化 - 自定义序列化程序以忽略空值

Kotlin - 无法序列化多态类。未找到序列化程序