Rollie
Rollie

Reputation: 4752

Is there a way to cast from 'String' to 'KType'?

Simply, I want a function like:

fun <T> convert(val foo: String, fooT: KType) : T {
    ...?
}

For Int, it would return foo.toInt(), for Double, foo.toDouble(), and to some unknown type, just throw an exception. I think it's not so hard to create my own switch statement for the types I expect, but out of curiosity - is there a way already?

Upvotes: 3

Views: 1566

Answers (1)

TheOperator
TheOperator

Reputation: 6446

Recommended way

Unfortunately, there's no easy generic way because we're not dealing with casts, but method calls. This would be my approach:

fun <T> convert(str: String, type: KType) : T {

    val result: Any = when (type.jvmErasure)
    {
        Long::class -> str.toLong()
        Int::class -> str.toInt()
        Short::class -> str.toShort()
        Byte::class -> str.toByte()
        ...
        else -> throw IllegalArgumentException("'$str' cannot be converted to $type")
    }

    return result as T // unchecked cast, but we know better than compiler
}

Usage:

@UseExperimental(ExperimentalStdlibApi::class)
fun main() {

    val int = convert<Int>("32", typeOf<Int>())

    println("converted: $int")
}

Instead of a KType parameter, you could also use a Class<T> and make the function reified, so it can be called as convert<Int>("32") or even "32".toGeneric<Int>().


Hardcore way

While there is no easy way, it is possible to access the type using heavy reflection and relying on implementation details. For this, we can extract the type name from the KType object, find an extension method (in a different class) that matches, and call it using reflection.

We have to use to*OrNull() instead of to*(), because the latter is inline and won't be found by reflection. Also, we need to resort to Java reflection -- at this time, Kotlin reflection throws UnsupportedOperationException for the types involved.

I do not recommend this in productive code, as it's inefficient and can break with future standard library versions, but it's a nice experiment:

fun convert(str: String, type: KType): Any {
    val conversionClass = Class.forName("kotlin.text.StringsKt") 
    // here, the to*OrNull() methods are stored
    // we effectively look for static method StringsKt.to*OrNull(String)

    val typeName = type.jvmErasure.simpleName
    val funcName = "to${typeName}OrNull" // those are not inline

    val func = try {
        conversionClass.getMethod(funcName, String::class.java) // Java lookup
    } catch (e: NoSuchMethodException) {
        throw IllegalArgumentException("Type $type is not a valid string conversion target")
    }

    func.isAccessible = true      // make sure we can call it
    return func.invoke(null, str) // call it (null -> static method)
            ?: throw IllegalArgumentException("'$str' cannot be parsed to type $type")
}

Upvotes: 5

Related Questions