Biswajit
Biswajit

Reputation: 75

Convert a string to a any data type in kotlin

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

Answers (1)

broot
broot

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

Related Questions