Пользовательский сериализатор с полиморфной сериализацией kotlinx

С полиморфизмом 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. Для сглаживания я переопределяю fun serialize(encoder: Encoder, obj : T) и fun deserialize(decoder: Decoder): T в Serializator, используя JsonOutput и JsonInput в соответствии с документацией в этих классах.

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<T>, 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 и кодировать объект Body с помощью Truck.Body.serializer () для сглаживания.

С этой сериализацией класс 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) next

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

Я получил правильный тип, но не могу сгладить объект Truck.Body с помощью его сериализатора.


person Est Stalegaykin    schedule 18.10.2019    source источник


Ответы (1)


правильный способ открытия и закрытия составного {} - это код

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

и вы сможете сериализовать Body с помощью .encodeSerializable(Body.serializer(), body)

и всегда передавайте дескриптор, иначе он вернется к вещам вроде LinkedhashMap для словаря json

person Nikky    schedule 21.10.2019
comment
В этом случае вы должны указать позицию элемента в дескрипторе, используя fun encodeStringElement (desc: SerialDescriptor, index: Int, value: String) или fun <T : Any?> encodeSerializableElement(desc: SerialDescriptor, index: Int, serializer: SerializationStrategy<T>, value: T) или что-то еще. Можно ли этого избежать? И .encodeSerializable(Body.serializer(), body) внутри beginStructure / endStructure образует что-то вроде {commonFields, {bodyFields}} Но мне нужна плоская структура `{commonFields, bodyFields}`. - person Est Stalegaykin; 22.10.2019