Confusing type error


#1

This little program is boiled down from some code uses reflection to turn Json into application objects.  That line with the ??? is confusing me. I've tried a couple casts and can't get it to type check.  Can anyone straighten out my thinking? What cast do I need?

Rob

enum class Foo { A; B }


fun main(args: Array<String>) {
  var cls = javaClass<Foo>()
  val x = fromJson(“A”, cls)
  println(x)
}


fun fromJson<T>(str: String, cls: Class<T>): T? {
  if (javaClass<java.lang.Enum<*>>().isAssignableFrom(cls)) {
  // we’ve found a json string and need to use it to create an instance of cls, which is an enum
  return java.lang.Enum.valueOf(cls, “A”)   // ???   
  }
  return null
}

The error:
Error:(17, 31) Kotlin: Type parameter bound for T in fun <T : kotlin.Enum<T>?> valueOf(p0: java.lang.Class<T>, p1: kotlin.String): T
is not satisfied: inferred type T is not a subtype of kotlin.Enum<T>?


#2

You should declare upper bound for T, like:

fun fromJson<T : kotlin.Enum<T>>(str: String, cls: Class<T>): T? {   if (javaClass<java.lang.Enum<*>>().isAssignableFrom(cls)) {   // we've found a json string and need to use it to create an instance of cls, which is an enum   return java.lang.Enum.valueOf(cls, "A")   // ???      }   return null }


#3

In the real code I can't do that, because `fromJson` is a larger function that is recursively creating objects from Json.  The top level object in not necessarily an enum. It discovers that a property of an object is an enum, and then we get to this code.  I tried creating a helper function with the signature you show, but then I have the same type error trying to call that helper.

Rob


#4

Here's the real function.

  fun fromJson<T>(json: JsonElement, cls: Class<T>): T? {
  
  if (json == JsonNull.INSTANCE) {
           return null
  }


  // These is tests seem to catch primitives and box types correctly.
  return when (json) {
           is JsonPrimitive -> {
           if (javaClass<java.lang.Enum<>>().isAssignableFrom(cls)) {
                   java.lang.Enum.valueOf<Enum<T>>(cls as Class<T>, json.getAsString())
                   // java.lang.Enum.valueOf(cls as Class<Enum<T>>, json.getAsString())
           }
           else {
                   when (cls) {
                   javaClass<Int>(), javaClass<java.lang.Integer>() -> json.getAsInt() as T
                   javaClass<Boolean>(), javaClass<java.lang.Boolean>() -> json.getAsBoolean() as T
                   javaClass<Double>(), javaClass<java.lang.Double>() -> json.getAsDouble() as T
                   javaClass<String>() -> json.getAsString() as T


                   /
                   is LocalDateTime -> {
                   val millis = (obj as LocalDateTime).atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli()
                   return JsonPrimitive(millis)
                   }
                   is java.util.Date -> {
                   val millis = obj.getTime()
                   return JsonPrimitive(millis)
                   }*/
                   else -> throw Exception(“JsonPrimitive: $json, javaClass: $cls”)
                   }
           }
           }
           is JsonArray -> {
           when (cls) {
                   else -> throw Exception(“JsonArray, javaClass: $cls”)
           }
           }
           is JsonObject -> {
           // todo: java.util.map case
           val obj = cls.newInstance()
           for ((name, subJson) in json.entrySet()!!) {
                   val setter = cls.getPropertySetter(name)
                   if (setter == null) {
                   throw Exception(“no setter from property ‘$name’”)
                   }
                   val subObj = fromJson<Any?>(subJson, setter.getParameterTypes()!![0] as Class<Any?>)
                   setter.invoke(obj, subObj)  // set property
           }
           obj
           }
           else -> throw Exception(“bah”)
  }
  }


#5

Workaround: create this Java and call it from Kotlin:

public class JavaHelp {
  public static Object makeEnum(Class cls, String str) {
  return Enum.valueOf(cls, str);
  }
}


#6

I am having the same issue. Has anyone found a pure-Kotlin solution?


#7

As far as I’ve gotten:
val result = java.lang.Enum.valueOf(clazz as Class<Nothing>, value)
My tentative understanding was that all Kotlin types were subtypes of Nothing, so it satisfies the enum extending itself requirement. But then this code throws a NullPointerException at runtime, after successfully calling the Java method and returning the enum value!
IDEA seems to know this in advance, as it marks all the code after this statement as “unreachable”. I’m pretty lost on this, so I guess I’ll go with the Java method for now.


#8

Nothing is a very special value to the Kotlin compiler. It is a subtype of every value but in many ways only exists conceptually. As in, it cannot actually be instantiated. It is meant for the compiler and allows signifying methods that do not return (eg. because they unconditionally throw an exception). What you need to realize here is that you don’t know the type of clazz at compile time. You may need to write a tiny helper (this could be an extension on Class instead.):

fun <T: java.lang.Enum<out<T>>> asEnumClass(clazz: Class<*>): T = clazz as T
val result: T = java.lang.Enum.valueOf(asEnumClass<T>(clazz), value)

#9

Thanks! I got that to work. Don’t know why I didn’t think of it myself. Snippet I ended up with for reference:

	println(instantiateEnum(DayOfWeek::class.java, "MONDAY"))
}
fun instantiateEnum(clazz: Class<*>, value : String) : Any {
	return java.lang.Enum.valueOf(asEnumClass<TimeUnit>(clazz), value)
}
fun <T: Enum<T>> asEnumClass(clazz: Class<*>): Class<T> = clazz as Class<T>```
Kind of cheating with type erasure (TimeUnit != DayOfWeek), but it works.