31

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

6 Answers 6

37

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 }
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks... that is about the point i got to as well..... if i make that function a method of the companion object of the class (and take out the generic) I can call it the way i wish.
If you want it to be limited to single enum class you could have added method in companion object that wraps valueOf with a try-catch block.
Thanks... that is about the point i got to as well. A top level function which is not what i want. Sort of one step further ..... if i make that function a method of the companion object of the enum class (and take out the generic) I can call it the way i wish. But how to have a generic method apply to the companion object of all enum classes is what i was hoping for,
32
Colors.values().find { it.name == "Yellow" }

4 Comments

a verbal explanation is often helpful
This is a very simple and elegant solution without the need for extension functions. You can also define a default value using the Elvis operator: Colors.values().find { it.name == "Yellow" } ?: Colors.Red
Best solution IMHO. If you want to default it then just do val colour = Colors.values().find { it.name == "Yellow" } ?: Colors.UNKNOWN or something like that.
Similar to @MickSear, I use Colors.values().firstOrNull { it.name == "Yellow" }?: Colors.UNKNOWN
19

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>()

Comments

3
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
    }
}

Comments

1

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 }
}

Using

if (type.isEnum) enumValueOf(#Field.type, value as String)

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.