Workaround for reified T at class level

Hello,
as I’ve read countless threads on here and stackoverflow it is not possible to have a reified T at class level as

class MyClass<reified T>()

But I am creating a class that is a generic base implementation for a websocket onMessage function that should take in a string and parse it into the Generic U before using it to do some routing.

for this I need to be able to provide a generic U of the type Message, but the code bellow doesn’t work as the function ‘validateMessage’ T to be reified, which isn’t possible with U at a class level.

Any idea about how to solve this problem?

open class WebSocket<out U: Message>(private val broadcaster: WebSocketBroadcaster) {
    open fun onMessage(
        message: String,
        session: WebSocketSession?
    ): Publisher<Message>? {
        val result = validateMessage<U>(message)
        val clientMessage: U = result.getOrElse {
            val newMessage = GameServerMessage(
                action = ServerAction.ERROR,
                text = "Invalid Message"
            )
            return session?.send(newMessage)
        }
        return route(clientMessage, session)
    }

    private fun route(message: Message, session: WebSocketSession?): Publisher<Message>? {
        for (function in this::class.declaredMemberFunctions) {
            val isOnActionFunction = function.annotations.any { it -> it is OnAction }
            if (isOnActionFunction) {
                for (par in function.parameters) {
                    println(par)
                }
                function.call(this, message, session)
            }
        }

        val newMessage = GameServerMessage(
            action = ServerAction.ERROR,
            text = "Invalid Message"
        )
        return session?.send(newMessage)
    }

    private inline fun <reified T> validateMessage(message: String): Result<T> {
        return try {
            val messageObject = Klaxon().parse<T>(message)
            messageObject?.let {
                return Result.success(it)
            }
            Result.failure<T>(NullPointerException())
        } catch(e: KlaxonException) {
            Result.failure<T>(e)
        } catch(e: IllegalArgumentException) {
            Result.failure<T>(e)
        }
    }
}

Error from code: Cannot use 'U' as reified type parameter. Use a class instead.

As the error says: Use a class instead. Add to your class’s primary constructor a property of type Class<U>, and use that to do the necessary casting &c. (You’ll probably find that the compiler will then infer U without you needing to specify that too.)

This isn’t an ideal approach — it’s awkward, and some types don’t correspond directly to classes (nullable types being the most obvious) — but I think it’s fairly common.

I already discussed this issue in an obsolete thread, however I leave you this snippet, I hope this helps.

import kotlin.reflect.KClass

fun main() {
    val test = Test<String>()
    println(test)
}

class Test<T : Any>(val type: KClass<T>) {

    override fun toString(): String = "Test<${type}>"

    companion object {
        inline operator fun <reified T : Any> invoke(): Test<T> = Test(T::class)
    }
}
4 Likes

In this case you can also define the method as an extension on WebSocket like this:

open class WebSocket<out U: Message>(private val broadcaster: WebSocketBroadcaster) {
    open fun doOnMessage(
        message: String,
        session: WebSocketSession?,
        result: Result<U>
    ): Publisher<Message>? {
        val clientMessage: U = result.getOrElse {
            val newMessage = GameServerMessage(
                action = ServerAction.ERROR,
                text = "Invalid Message"
            )
            return session?.send(newMessage)
        }
        return route(clientMessage, session)
    }

    private fun route(message: Message, session: WebSocketSession?): Publisher<Message>? {
        for (function in this::class.declaredMemberFunctions) {
            val isOnActionFunction = function.annotations.any { it -> it is OnAction }
            if (isOnActionFunction) {
                for (par in function.parameters) {
                    println(par)
                }
                function.call(this, message, session)
            }
        }

        val newMessage = GameServerMessage(
            action = ServerAction.ERROR,
            text = "Invalid Message"
        )
        return session?.send(newMessage)
    }

    private inline fun <reified T> validateMessage(message: String): Result<T> {
        return try {
            val messageObject = Klaxon().parse<T>(message)
            messageObject?.let {
                return Result.success(it)
            }
            Result.failure<T>(NullPointerException())
        } catch(e: KlaxonException) {
            Result.failure<T>(e)
        } catch(e: IllegalArgumentException) {
            Result.failure<T>(e)
        }
    }
}

inline fun <reified U> WebSocket<U>.onMessage(message: String, session: WebSocketSession?) = doOnMessage(message, session, validateMessage<U>(message))

Thank you for the snippet. However, I don’t think this works for my case, or I might just not understand how to properly use it.

As far as i understand that snippet basically just autofills the type argument with T when creating a new Test object, instead of just writing Test(String::class)?

Trying to implement it I still get that Cannot use 'U' as reified type parameter. Use a class instead. which stems from this line

val result = validateMessage<U>(message)

trying to replace U with ‘type’, ‘this’, ‘WebSocket’ etc. doesn’t work.

1 Like