1. Overview
Enums in Kotlin make handling constants type-safe, less error-prone, and self-documented.
In this tutorial, we’ll explore how to convert a string into an enum object.
2. Introduction to the Problem
As usual, let’s understand the problem through an example. Let’s say we have the enum class BaeldungNumber:
enum class BaeldungNumber {
ONE, TWO, THREE, FOUR, FIVE, UNKNOWN,
}
Assuming we’re given a string, for instance, “TWO”, we want to get the enum object BaeldungNumber.TWO as a result. In other words, we want to check enum objects’ names and return the instance whose name matches the given string.
The requirement can have a few variants, such as whether we want the string check to be case-insensitive, what result we shall return if no enum instance is found, and so on.
Next, we’ll address several approaches to converting a string into an enum instance and discuss these requirement variants.
For simplicity, we’ll use unit test assertions to verify if our solutions work as expected.
3. Using Enum Class’s valueOf() Function
In Kotlin, each enum class has a valueOf() function. The valueOf() function is exactly what we’re looking for, as it allows us to get an enum constant by its name.
For example, this test passes:
assertEquals(ONE, BaeldungNumber.valueOf("ONE"))
As we can see, using the valueOf() function is pretty straightforward. However, when we use the function to get a constant of an enum by name, we need to note a couple of things:
- To find an enum constant, the given string must exactly match the enum constant name.
- In case no constant can be found, the function throws IllegalArgumentException.
We can verify them via a simple test:
assertThrows<IllegalArgumentException> {
BaeldungNumber.valueOf("one")
}
4. Using the enumValueOf() Function
The enumValueOf() function is pretty similar to the valueOf() function. The only difference is that *using enumVallueOf(), we can access constants in an enum class in a generic way*.
Next, let’s write a test to see how to call the function:
val theOne = enumValueOf<BaeldungNumber>("ONE")
assertEquals(ONE, theOne)
assertThrows<IllegalArgumentException> {
enumValueOf<BaeldungNumber>("one")
}
The test passes if we run it. Therefore, like the valueOf() function, enumValueOf() does an exact match on the constant name and also raises an IllegalArgumentException if no match is found.
5. Checking Through All Constants
Both valueOf() and enumValueOf() approaches are easy to use. But they do exact string matches and throw an exception when no match is found. Sometimes, this isn’t convenient. For example, if we want to find the constant by name case-insensitively or get a null value if no match is found.
Another idea to solve the problem is to walk through the enum instances and check their names per our required rule, such as case-insensitive.
Enum’s values() function returns all constants in an array. So, we can check each constant in the returned array until we find the expected constant:
val theOne = BaeldungNumber.values().firstOrNull { it.name.equals("one", true) }
assertEquals(ONE, theOne)
val theTwo = BaeldungNumber.values().firstOrNull { it.name.equals("TWO", true) }
assertEquals(TWO, theTwo)
val theNull = BaeldungNumber.values().firstOrNull { it.name.equals("whatever", true) }
assertNull(theNull)
As the code above shows, the firstOrNull() function is responsible for finding the matched constant. If no match is found, it returns a null value.
Further, we apply our own match logic (the case-insensitive equal check) as the prediction function in firstOrNull(). The test passes if we give it a run.
In practice, we may want to create a function in the enum’s companion object block:
enum class BaeldungNumber {
ONE, TWO, THREE ...
companion object {
fun byNameIgnoreCaseOrNull(input: String): BaeldungNumber? {
return values().firstOrNull { it.name.equals(input, true) }
}
}
}
Then we can call it as it’s a “static” function:
assertEquals(ONE, BaeldungNumber.byNameIgnoreCaseOrNull("one"))
assertNull(BaeldungNumber.byNameIgnoreCaseOrNull("whatever"))
6. The Default Constant Support
Our byNameIgnoreCaseOrNull() function supports case-insensitive match and returns null value instead of throwing an exception in case no constant is found. However, sometimes we’d like to have a default constant for the no-match-found case, for instance, the BaeldungNumber.UNKNOWN constant. Further, it’d be good if this function is generic to work for all enum classes without changing the enums.
Next, let’s create a function to achieve that:
inline fun <reified T : Enum<T>> enumByNameIgnoreCase(input: String, default: T? = null): T? {
return enumValues<T>().firstOrNull { it.name.equals(input, true) } ?: default
}
As the function above shows, we’ve introduced a new nullable default argument to the enumByNameIgnoreCase() function. Also, we assigned a default value to the argument: null.
Further, we’ve used the standard reified enumValues function to get all instances of Enum
Next, let’s create a test to verify if the function works as expected:
val theOne = enumByNameIgnoreCase<BaeldungNumber>("ONE")
assertEquals(ONE, theOne)
val theTwo = enumByNameIgnoreCase<BaeldungNumber>("two")
assertEquals(TWO, theTwo)
val theNull = enumByNameIgnoreCase<BaeldungNumber>("whatever")
assertNull(theNull)
val theDefault = enumByNameIgnoreCase("whatever", UNKNOWN)
assertEquals(UNKNOWN, theDefault)
When we run the test, it passes. So, the enumByNameIgnoreCase() function is pretty flexible.
It can find a constant by name case-insensitively. Here’s how it behaves for the no-match-found cases:
- If we don’t define a default value, the function returns null.
- If we pass a default argument, for example, UNKNOWN, the default value will be returned.
7. Conclusion
In this article, we’ve learned how to convert a string into an enum constant.
As always, the full source code used in the article can be found over on GitHub.