3

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}")
}
androidguy
  • 3,005
  • 2
  • 27
  • 38
  • Sorry for maybe stupid question, but what is a problem with using `if (enumval.toString() == str)`? – Michael Spitsin Oct 25 '16 at 06:18
  • This is true - as long as I decide to override toString like this, I do not need to use any `JsonStringConvertible` or anything like that. – androidguy Oct 25 '16 at 20:16
  • hi, saw your update. Nice that could help you somehow. According to your updated question you can check here to find out how to return generic type from `class` parameter: http://stackoverflow.com/questions/34122450/how-to-get-type-info-for-a-generic-parameter – Michael Spitsin Oct 26 '16 at 06:26
  • Also you can check this part of documentation: https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters – Michael Spitsin Oct 26 '16 at 06:33
  • Last thing is answer to question about best way: there is no best way and all things depends on needs and circumstances. In your case for me best way is using toString because 1) I don't see more context so this solution is fine; 2) in case of interface we can make another implementations (not enums); 3) it will claim to write additional code to provide casting to your concrete enum type; 4) toString in your case is logic string representation of type (and you should implement toString, check out Effective java from Joshua Bloch) – Michael Spitsin Oct 26 '16 at 06:37

2 Answers2

2

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.

miensol
  • 39,733
  • 7
  • 116
  • 112
  • Thanks. I got most of the way here. I had `val jsonStr: String` in the Religousness constructor. Got the error "'jsonStr' hides member of supertype ...". Not sure why it needs `override` modifier. I guess I just spaced out that 'override' was required. By the way, do you have any thought about how to get rid of the floating function and write it is class extension or something like that? – androidguy Oct 25 '16 at 19:05
  • @user3175580 what do you mean by "floating function"? – miensol Oct 26 '16 at 04:40
1

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
}
androidguy
  • 3,005
  • 2
  • 27
  • 38