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

If you have a series of possible messages in your data model, what you probably need is a sealed class.

When making a sealed class @Serializable, the compiler will automatically add the the field type in the serialized object. By default is the fully qualified name of the class but you can customize it using @SerialName.

have a look at the polymorphic section of Kotlinx.serialization.

2 Likes

Ah, right, I remember reading about that when starting out on serialization, completely forgot that, thanks, it solved my problem.

My question still stands though, so if anybody knows if there’s a way to use a reference to a type/class as a reified parameter to instantiate and Object using (de)serialization, that would be really interesting.