innov8
innov8

Reputation: 2219

kotlin safe conversion from string to enum

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

Answers (6)

Tatsuya Fujisaki
Tatsuya Fujisaki

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

Pawel
Pawel

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

Braian Coronel
Braian Coronel

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

Using

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

Upvotes: 1

Fab
Fab

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

Roberto
Roberto

Reputation: 4899

Colors.values().find { it.name == "Yellow" }

Upvotes: 32

ymkjp
ymkjp

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

Related Questions