Geosearchef
Geosearchef

Reputation: 648

Using a KClass reference as a reified parameter to deserialize from JSON

I'm trying to implement a general serialization framework to convert outgoing and incoming messages to json using the kotlinx serialialization. I'm developing a multiplatform app, so I'm trying to get it to run on KotlinJVM and KotlinJS.

For this, I add a type field to every message and use a map that maps each type string to a KClass. What's the type for that map? It contains KClass<> objects whose classes extend the Message class, therefore in java I'd specify my map as Map<KClass<? extends Message>, String>.

How can I do that in Kotlin?

Afterwards I need to serialize and deserialize the message based on its key and therefore type. Java frameworks take a Class parameter for the type of the object I want to deserialize/instantiate (e.g. gson.fromJson(ClientMessage.class)). In Kotlin this is done using reified parameters Json.decodeFromString<Type>. I do not know the type of the message at compile time though and just have a reference to a KClass, how can I instantiate an object based on that?

@Serializable
open class Message(val type: String) {

    companion object {
        val messageTypes: Map<KClass<out Message>, String> = mapOf(
            ClientLoginMessage::class to "clientLoginMessage",
            Message::class to "message"
        )

        inline fun <reified T> getMessageTypeByClass(): String = messageTypes[T::class]!! // utility for defining the type in the constructors of the individual messages
    }

    fun toJson() = Json.encodeToString(this)

    fun fromJson(json: String): Message? {
        val plainMessage = Json.decodeFromString<Message>(json) // get type string from json
        return messageTypes.entries.find { it.value == plainMessage.type }?.let {
            // how can I use the KClass from it.key as reified parameter?
            Json.decodeFromString<?????>(json) 
        }
    }
}

@Serializable
class ClientLoginMessage
         : Message(Message.getMessageTypeByClass<ClientLoginMessage>()) {}

Upvotes: 0

Views: 1348

Answers (1)

Alexander Egger
Alexander Egger

Reputation: 5300

Create a map of serializers like for types:

val serializers: Map<KClass<out Message>, KSerializer<out Message>> = mapOf(
            ClientLoginMessage::class to ClientLoginMessage.serializer(),
            Message::class to Message.serializer()
        )


Pass in the serializer needed to Json.decodeFromString like this:

fun fromJson(json: String): Message? {
        val plainMessage = Json.decodeFromString<Message>(json) // get type string from json
        return messageTypes.entries.find { it.value == plainMessage.type }?.let {
            // how can I use the KClass from it.key as reified parameter?
            Json.decodeFromString(serializers.get(plainMessage.type)!!, json)
        }
    }

You might also want to have a look at the Kotlin built in handling of polymorphic classes: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md

Upvotes: 1

Related Questions