Est Stalegaykin
Est Stalegaykin

Reputation: 53

Custom serializer with polymorphic kotlinx serialization

With kotlinx.serialization polymorphism, I want to get

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

but I get

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

I use the following models

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

I apply the following Json

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

I use serializer TruckKSerializer because the server adopts a flat structure. At the same time, in the application I want to use an object Truck.Body. For flatten I override fun serialize(encoder: Encoder, obj : T) and fun deserialize(decoder: Decoder): T in Serializator using JsonOutput and JsonInput according to the documentation in these classes.

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

And finally, I use stringify(serializer: SerializationStrategy<T>, obj: T)

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

I end up with {"type":"kotlin.collections.LinkedHashMap", ...}, but I need {"type":"veh_t", ...} How do I get the right type? I want using polymorphism for Vehicle and encode Body object with Truck.Body.serializer() to flatten.

With this serialization, the PassengerCar class runs fine.

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

Result is correct:

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

I think the problem is the custom serializer TruckKSerializer. And I noticed if I use in my overridden fun serialize(encoder: Encoder, obj : T) next code

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

I get the correct type but cannot flatten the object Truck.Body using its serializer.

Upvotes: 2

Views: 3859

Answers (1)

Nikky
Nikky

Reputation: 518

the correct way to open and close a composite {} is this code

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

and you should be able to serialize Body using .encodeSerializable(Body.serializer(), body)

and always pass the descriptor along otherwise it will fall back to stuff like that LinkedhashMap for the json dictionary

Upvotes: 0

Related Questions