Search code examples
scalareflectionintrospectionscala-2.10

Getting the parameters of a case class through Reflection


As a follow up of Matt R's question, as Scala 2.10 has been out for quite an amount of time, what would be the best way to extract the fields and values of a case class. Taking a similar example:

case class Colour(red: Int, green: Int, blue: String) {
  val other: Int = 42
} 

val RBG = Colour(1,3,"isBlue")

I want to get a list (or array or any iterator for that matter) that would have the fields declared in the constructor as tuple values like these:

[(red, 1),(green, 3),(blue, "isBlue")]

I know the fact that there are a lot of examples on the net regarding the same issue but as I said, I wanted to know what should be the most ideal way to extract the required information


Solution

  • If you use Scala 2.10 reflection, this answer is half of the things you need. It will give you the method symbols of the case class, so you know the order and names of arguments:

    import scala.reflect.runtime.{universe => ru}
    import ru._
    
    def getCaseMethods[T: TypeTag] = typeOf[T].members.collect {
      case m: MethodSymbol if m.isCaseAccessor => m
    }.toList
    
    case class Person(name: String, age: Int)
    
    getCaseMethods[Person]  // -> List(value age, value name)
    

    You can call .name.toString on these methods to get the corresponding method names.

    The next step is to invoke these methods on a given instance. You need a runtime mirror for that

    val rm = runtimeMirror(getClass.getClassLoader)
    

    Then you can "mirror" an actual instance:

    val p  = Person("foo", 33)
    val pr = rm.reflect(p)
    

    Then you can reflect on pr each method using reflectMethod and execute it via apply. Without going through each step separately, here is a solution altogether (see the val value = line for the mechanism of extracting a parameter's value):

    def caseMap[T: TypeTag: reflect.ClassTag](instance: T): List[(String, Any)] = {
      val im = rm.reflect(instance)
      typeOf[T].members.collect {
        case m: MethodSymbol if m.isCaseAccessor =>
          val name  = m.name.toString
          val value = im.reflectMethod(m).apply()
          (name, value)
      } (collection.breakOut)
    }
    
    caseMap(p) // -> List(age -> 33, name -> foo)