Reputation: 75
I am trying to create a generic function to convert any string to any data type. For example
convert<Int>("23") => 23
convert<LocalDateTime>("2022-06-29T18:02:00") => LocalDateTime object
convert<String>("hg") => "hg"
and throw an error if not possible.
This is the code used to do that without any error handling.
/**
* Convert a string to given class type or throw an error if it is not valid
*/
inline fun <reified T> stringToTypeOrError(value: String): T {
val v: T = when (T::class) {
Int::class -> value.toInt() as T
String::class -> value as T
Double::class -> value.toDouble() as T
LocalDateTime::class -> LocalDateTime.parse(value) as T
else -> throw BadRequestException("Does Not Support : $value.")
}
return v
}
But I face a problem when I try to convert string to enums. For example, if I need to convert to an enum.
convert<Order>("asc") => Order.ASC
For supporting all enums I tried this:
enum class Order { ASC, DESC }
/**
* Convert a string to enum or throw a custom error
*/
inline fun <reified E : Enum<E>> enumValueOrError(value: String): E {
return try {
enumValueOf(value)
} catch (e: java.lang.IllegalArgumentException) {
throw BadRequestException("Does Not Support : $value.")
}
}
/**
* Convert a string to a given class type or throw an error if it is not valid
*/
inline fun <reified T> stringToTypeOrError(value: String): T {
// TODO: Handle all exceptions
val v: T = when (T::class) {
Int::class -> value.toInt() as T
String::class -> value as T
Double::class -> value.toDouble() as T
LocalDateTime::class -> LocalDateTime.parse(value) as T
else -> {
if (T::class.javaClass.isEnum) {
return enumValueOrError<T>(value)
} else {
throw NotImplementedError("Not implemented for type:${T::class.qualifiedName}")
}
}
}
return v
}
build fails saying:
Type argument is not within its bounds: should be subtype of 'Enum<TypeVariable(E)>'
in line return enumValueOrError<T>(value)
Any ideas on how to solve this?
Or how can i pass the generic type T
in enumValueOrError
function
(Without adding each enum inside the when
case)
Upvotes: 0
Views: 1393
Reputation: 28322
This case is very similar to unchecked casts where we usually suppress the warning. Even if in your case this is not a warning, but an error, Kotlin still allows us to suppress it:
@Suppress("UPPER_BOUND_VIOLATED")
return enumValueOrError<T>(value)
Additionally, you have a bug in your code where you check for T::class.javaClass.isEnum
. You should check for T::class.java.isEnum
instead. You can also skip Java reflection entirely with: T::class.isSubclassOf(Enum::class)
.
Finally, it seems not a good idea to create such a big inline function. It will greatly affect the size of the resulting bytecode. I suggest to split this function into two: internal function that receives KClass<T>
and inline reified function that just invokes the first one. Unfortunately, this way you'll lose ability to use enumValueOf()
utility.
Upvotes: 2