androidguy
androidguy

Reputation: 3177

How to write a reusable transform for String to Enum value across a group of Enum classes? (Kotlin)

I have a group >5 of Enum classes that take String parameter in its values, and I want to have simple code for all these Enum classes to convert from a String field in JSON object.

enum class Religiousness(val jsonStr: String, val resID: Int) {
    NotAtAll("none", R.string.not_religious),
    Somewhat("somewhat", R.string.somewhat_religious),
    Very("very", R.string.very_religious),
    ;
    override fun toString() = jsonStr
    fun displayString(res: Resources) = res.getString(resID)
}

I want to be able to write code like this

fun JsonConvertStrToEnum(enumClass: Class<Enum<*>>, str: String): Enum<*> {
    for (enumval in enumClass.enumConstants) {
        if ((enumval as IJsonStringConvertible).jsonStr() == str)
            return enumval
    }
    throw IllegalArgumentException("Gave an invalid enum value for class ${enumClass.canonicalName}")
}

I am having a hard time figuring out if IJsonStringConvertible can work, and what its definition would be, and how to implement it in the Enum value instances. Any advice?

Update: I have now written the converter as this. Is this the best way? Can I also express that the return value is a subtype of the parameter so don't need to cast return value?

fun JsonConvertStrToEnum(enumClass: Class<out Enum<*>>, str: String): Enum<*> {
    for (enumval in enumClass.enumConstants) {
        if (enumval.toString() == str)
            return enumval
    }
    throw IllegalArgumentException("Gave an invalid enum value for class ${enumClass.canonicalName}")
}

Upvotes: 3

Views: 1328

Answers (2)

androidguy
androidguy

Reputation: 3177

If it helps anyone, here's the final version in my production app.

fun <EnumT : Enum<EnumT>> ConvertStrToEnum(enumClass: Class<EnumT>, str: String?): EnumT? {
    if (str == null)
        return null
    for (enumval in enumClass.enumConstants) {
        if (enumval.toString() == str)
            return enumval
    }
    throw IllegalArgumentException("Gave an invalid enum value for class ${enumClass.canonicalName}")
}

fun <EnumT : Enum<EnumT> > ConvertStrArrayToEnumSet(enumClass: Class<EnumT>, array: List<String>?) : EnumSet<EnumT> {
    val set = EnumSet.noneOf(enumClass)
    array?.forEach { value -> set.add(ConvertStrToEnum(enumClass, value)) }
    return set
}

Upvotes: 1

miensol
miensol

Reputation: 41678

Enums as other classes can implement interfaces like so:

interface IJsonStringConvertible {
    val jsonStr:String
}

enum class Religiousness(override val jsonStr: String, val resID: Int) : IJsonStringConvertible {
    NotAtAll("none", R.string.not_religious),
    Somewhat("somewhat", R.string.somewhat_religious),
    Very("very", R.string.very_religious),
    ;

    override fun toString() = jsonStr
    fun displayString(res: Resources) = res.getString(resID)
}

Which would then be used as:

for (enumval in enumClass.enumConstants) {
    if ((enumval as IJsonStringConvertible).jsonStr == str)
        return enumval
}

However the above lookup can be expensive (if used millions of times). Take a look at the reverse lookup question to find out how to do it more efficiently.

Upvotes: 2

Related Questions