Reputation: 4752
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
Reputation: 6446
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>()
.
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