Any way to access the type of a Scala Option declaration at runtime using reflection?
-
27-09-2019 - |
Question
So, I have a Scala class that looks like this:
class TestClass {
var value: Option[Int] = None
}
and I'm tackling a problem where I have a String value and I want to coerce it into that Option[Int] at runtime using reflection. So, in another piece of code (that knows nothing about TestClass) I have some code like this:
def setField[A <: Object](target: A, fieldName: String, value: String) {
val field = target.getClass.getDeclaredField(fieldName)
val coercedValue = ???; // How do I figure out that this needs to be Option[Int] ?
field.set(target, coercedValue)
}
To do this, I need to know that the field is an Option and that the type parameter of the Option is Int.
What are my options for figuring out that the type of 'value' is Option[Int] at runtime (i.e. using reflection)?
I have seen similar problems solved by annotating the field, e.g. @OptionType(Int.class). I'd prefer a solution that didn't require annotations on the reflection target if possible.
Solution
It's quite streightforward using Java 1.5 reflection API:
def isIntOption(clasz: Class[_], propertyName: String) = {
var result =
for {
method <- cls.getMethods
if method.getName==propertyName+"_$eq"
param <- method.getGenericParameterTypes.toList.asInstanceOf[List[ParameterizedType]]
} yield
param.getActualTypeArguments.toList == List(classOf[Integer])
&& param.getRawType == classOf[Option[_]]
if (result.length != 1)
throw new Exception();
else
result(0)
}
OTHER TIPS
At the byte code level, Java hasn't got Generics. Generics are implemented with polymorphism, so once your source code (in this case Scala) is compiled, generic types disappear (this is called type erasure ). This makes no possible to gather Generic runtime type information via reflection.
A possible --though little dirty-- workaround is to obtain the runtime type of a property you know it has the same type as the Generic parameter. For Option instances we can use get
member
object Test {
def main(args : Array[String]) : Unit = {
var option: Option[_]= None
println(getType(option).getName)
option = Some(1)
println(getType(option).getName)
}
def getType[_](option:Option[_]):Class[_]= {
if (option.isEmpty) classOf[Nothing] else (option.get.asInstanceOf[AnyRef]).getClass
}
}
class TestClass {
var value: Option[Int] = None
// ...
def doSomething {
value match {
case Some(i) => // i is an Int here
case None =>
// No other possibilities
}
}
}
The problem is that JVM implements generics through type erasure. So it's impossible to discover through reflection that the type of value
is Option[Int]
because at the run-time it actually isn't: it's just Option
!
In 2.8 you should be able to use Manifests
like this:
var value: Option[Int] = None
val valueManifest = implicitly[scala.reflect.Manifest[Option[Int]]]
valueManifest.typeArguments // returns Some(List(Int))