Hello, World! I have a pattern that happens a lot...
# ktor
b
Hello, World! I have a pattern that happens a lot in our codebase: enums with a String key, and we need to get the enum value from the key, or a special “unknown” value. So what we do currently is something like:
Copy code
enum class MyEnum(val key: String) {
    FOO("foo"),
    BAR("bar"),
    UNKNOWN("unknown");

    companion object {
        fun getByKey(key: String?): MyEnum {
            return MyEnum.values().firstOrNull { it.key == key } ?: MyEnum.UNKNOWN
        }
    }
}
I’m looking for a way to not repeat this companion object for all our enums. Possibly a combination of an interface and a generic extension function? Any idea? 🙂
d
An extension method on Enum and having enum implements an interface like
interface LookableByKey { val key: String }
?
(btw not sure this is related to ktor...)
m
Continuing with @dany's suggestion. You can do something like this:
Copy code
interface EnumCompanion<T : LookableByKey<*>> {
    val default: T
    fun values(): Array<T>
    fun getByKey(key: String?): T = values().firstOrNull { it.key == key } ?: default
}

interface LookableByKey<T> {
    val key: T
}

enum class MyEnum(override val key: String): LookableByKey<String> {
    FOO("foo"),
    BAR("bar"),
    UNKNOWN("unknown");

    companion object : EnumCompanion<MyEnum> {
        override fun values() = MyEnum.values()
        override val default = UNKNOWN
        
    }
}
b
wow, I’m very sorry, I posted this in the wrong channel! Was meaning to post in #general
but thanks a lot for the answer. @marstran It’s cool but still a bit a boilerplate per enum 🙂 I wonder if there could be an even better solution
I forgot companion object can inherit something, so that’s very cool
I hope you won’t find it rude that I’m gonna repost this to #general
d
why not having an extension on Enum something like
fun <T> Enum<T>.getByKey() : Enum<T>
?
m
You will get some boilerplate per enum no matter what you do. The extension needs to know what the default-value is, and how to extract the key. And because
values()
is not part of the
Enum
class, you have to provide that function as well.
b
@danielm that was my first try but couldn’t achieve anything
m
@dany Because that will require you to have an enum value from before. Like, you can only call it with
BAR.getByKey()
. It won't allow you to do
MyEnum.getByKey()
.
b
there’s also
enumValueOf
, I wonder if it could be of any help
d
indeed marius! missed that tks
m
@bod Ah, right. There's also
enumValues
. That may make it simpler. 2 sec
Or not. The type parameter has to be reified and you can only reify type parameters on inline functions.
b
oh well 🙂
thanks a lot anyway!
m
Oh, I managed to make it work.
Copy code
interface EnumCompanion<T> {
    val default: T
}

inline fun <reified T> EnumCompanion<T>.getByKey(key: String?): T
where T : Enum<T>, T : LookableByKey<*> {
    return enumValues<T>().find { it.key == key } ?: default
}

interface LookableByKey<T> {
    val key: T
}

enum class MyEnum(override val key: String): LookableByKey<String> {
    FOO("foo"),
    BAR("bar"),
    UNKNOWN("unknown");

    companion object : EnumCompanion<MyEnum> {
        override val default = UNKNOWN
    }
}
👍 1
b
wow, not that’s definitely cool!
never seen
where
used before, I guess that’ll be a first for me 😉
m
Hehe, you need it when the type parameter has more than one constraint.
👍 1
@bod You can actually get rid of the
LookableByKey
interface as well if you change the
EnumCompanion
to this:
Copy code
interface EnumCompanion<T, K> {
    fun T.key(): K
    val default: T
}
b
even better! 👍