Reputation: 2219
I need to convert strings to Enum values, but want a function which returns null if the string is not an enum.
enum class Colors{
Red, Green, Blue
}
I can used Colors.valueOf(testString) provided testString is value, but there will be an exception if it is not valid, and I want a null in that case.
Because I want to this often, an extension function would be ideal. But the extension needs to operate on the class Colors, and not an object of type Colors.
Anyone know how to write such an extension? Ideally one that is generic for any enum class.
It is simple to write a top level function, but I am seeking one that acts as the standard 'method' does
// instead of
val willGetAnException = Colors.valueOf("Yellow") // standard existing fun
val willGetNull = Colors.valueOrNullOf("Orange") // new fun i seek
And ideally one that is generic and works for any enum
Upvotes: 30
Views: 35895
Reputation: 2001
enum class Colors {
BLACK, WHITE, UNKNOWN;
companion object {
// Verbose for illustrative purposes
fun fromOrdinal(ordinal: Int): Colors = entries[ordinal]
fun fromOrdinalOrNull(ordinal: Int): Colors? = entries.getOrNull(ordinal)
fun fromOrdinalOrDefault(ordinal: Int): Colors = entries.getOrElse(ordinal) { UNKNOWN }
fun fromName(name: String): Colors = valueOf(name.uppercase())
fun fromNameOrNull(name: String): Colors? = entries.find { it.name == name.uppercase() }
fun fromNameOrDefault(name: String): Colors = entries.find { it.name == name.uppercase() } ?: UNKNOWN
}
}
Upvotes: 3
Reputation: 17288
You don't want an extension since they must be invoked on an existing object. You want a top-level function. There is a built in one You can use:
/**
* Returns an enum entry with specified name.
*/
@SinceKotlin("1.1")
public inline fun <reified T : Enum<T>> enumValueOf(name: String): T
You can call it by inferring the type, or explicitly:
val a : MyEnumClass = enumValueOf("A")
val b = enumValueOf<MyEnumClass>("B")
However this method is not nullable: it throws java.lang.IllegalArgumentException
on unknown values.
But it's easy to mimick its behavior and have it work for nullable enums with a top level function:
inline fun <reified T : Enum<*>> enumValueOrNull(name: String): T? =
T::class.java.enumConstants.firstOrNull { it.name == name }
Upvotes: 35
Reputation: 22905
The Kotlin API does not work by simply using <reified T: Enum<T>>
. It throws an exception of the type InvocationTargetException
. So I pass directly to type: Class<T>
by parameter.
private fun <T> enumValueOf(type: Class<T>, enum: String) : T {
return type.enumConstants.first { it.toString() == enum }
}
if (type.isEnum) enumValueOf(#Field.type, value as String)
Upvotes: 1
Reputation: 361
You can use something like this :
inline fun <reified T : Enum<T>> String.asEnumOrDefault(defaultValue: T? = null): T? =
enumValues<T>().firstOrNull { it.name.equals(this, ignoreCase = true) } ?: defaultValue
Then: "Yellow".asEnumOrDefault(Colors.Green)
Or, if you it can't be infered: "Yellow".asEnumOrDefault<Colors>()
Upvotes: 18
Reputation: 91
Given the fact it's not easy to access the Enum value safely in Kotlin, I published a library enum-or-null-kt to provide a collection of shorthand functions which makes you can write code like below:
class Example {
enum class Direction(val az: Int) {
NORTH(0),
EAST(90),
SOUTH(180),
WEST(240)
}
fun printAz01(name: String = "EAST") {
val direction = enumValueOrNull<Direction>(name) ?: Direction.EAST
println("az01=${direction.az}")
}
fun printAz02(name: String = "EAST") {
val direction = name.toEnumOrNull<Direction>() ?: Direction.EAST
println("az02=${direction.az}")
}
fun printName01(az: Int = 0) {
val direction = enumValueOrNull<Direction> {
it.az == az
} ?: Direction.NORTH
println("name03=${direction.name}")
}
fun printName02(ordinal: Int = 0) {
val direction = enumValueOrNull<Direction> {
it.ordinal == ordinal
} ?: Direction.NORTH
println("name03=${direction.name}")
}
}
With it, not only can you access the Enum value with names, but also you can pass an arbitrary higher-order function as a predicate clause. That is convenient when you need to deal with a custom conversion such as JPA attribute converters.
Upvotes: 0