30

How can I extract the field values from a case class in scala using the new reflection model in scala 2.10? For example, using the below doesn't pull out the field methods

  def getMethods[T:TypeTag](t:T) =  typeOf[T].members.collect {
    case m:MethodSymbol => m
  }

I plan to pump them into

  for {field <- fields} {
    currentMirror.reflect(caseClass).reflectField(field).get
  }
samthebest
  • 30,803
  • 25
  • 102
  • 142
J Pullar
  • 1,915
  • 2
  • 18
  • 30

2 Answers2

54

MethodSymbol has an isCaseAccessor method that allows you to do precisely this:

def getMethods[T: TypeTag] = typeOf[T].members.collect {
  case m: MethodSymbol if m.isCaseAccessor => m
}.toList

Now you can write the following:

scala> case class Person(name: String, age: Int)
defined class Person

scala> getMethods[Person]
res1: List[reflect.runtime.universe.MethodSymbol] = List(value age, value name)

And you get only the method symbols you want.

If you just want the actual field name (not the value prefix) and you want them in the same order then:

def getMethods[T: TypeTag]: List[String] =
  typeOf[T].members.sorted.collect {
    case m: MethodSymbol if m.isCaseAccessor => m.name.toString
  }
samthebest
  • 30,803
  • 25
  • 102
  • 142
Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • Ah I realise now that my approach was wrong. Any idea how to get the caseAccessors from an unknown case class? IE one which is currently stored in as val SomeCaseClass:Any – J Pullar Apr 18 '13 at 10:08
  • Wait, no got it currentMirror.reflect(someCaseClass).symbol.asType.typeSignature.members – J Pullar Apr 18 '13 at 10:11
  • Can this work with multi-thread environment under scala 2.10 ? – jilen Apr 12 '14 at 12:23
  • 4
    Any way to make this return the methods in the same order they where declared?? – samthebest Oct 03 '14 at 15:30
  • 13
    @samthebest: Yes! Just add `.sorted` after `members` (see [the `MemberScope` docs for details](http://www.scala-lang.org/api/2.11.2/scala-reflect/index.html#scala.reflect.api.Scopes$MemberScope)). – Travis Brown Oct 03 '14 at 16:25
  • This would be handy to be directly part of `case class`es – WestCoastProjects Jul 21 '19 at 19:09
  • I can't get this to compile due to `Symbols` is a `Trait` so its components - specifially `MethodSymbol` - can not be imported. Can you expand the example to include the imports of `Symbols` and `MethodSymbol` ? – WestCoastProjects Jul 21 '19 at 19:20
  • @javadba This is a six year old answer and I'm not terribly interested in updating it myself, but feel free to edit—it probably just needs a `scala.reflect.runtime.universe._ ` import. – Travis Brown Jul 23 '19 at 07:11
14

If you want to get fancier you can get them in order by inspecting the constructor symbol. This code works even if the case class type in question has multiple constructors defined.

  import scala.collection.immutable.ListMap
  import scala.reflect.runtime.universe._

  /**
    * Returns a map from formal parameter names to types, containing one
    * mapping for each constructor argument.  The resulting map (a ListMap)
    * preserves the order of the primary constructor's parameter list.
    */
  def caseClassParamsOf[T: TypeTag]: ListMap[String, Type] = {
    val tpe = typeOf[T]
    val constructorSymbol = tpe.decl(termNames.CONSTRUCTOR)
    val defaultConstructor =
      if (constructorSymbol.isMethod) constructorSymbol.asMethod
      else {
        val ctors = constructorSymbol.asTerm.alternatives
        ctors.map(_.asMethod).find(_.isPrimaryConstructor).get
      }

    ListMap[String, Type]() ++ defaultConstructor.paramLists.reduceLeft(_ ++ _).map {
      sym => sym.name.toString -> tpe.member(sym.name).asMethod.returnType
    }
  }
solidsnack
  • 1,631
  • 16
  • 26
Connor Doyle
  • 1,812
  • 14
  • 22
  • I'm finding cases where this fails with `scala.ScalaReflectionException: is not a term` in the expression `constructorSymbol.asTerm.alternatives`. The documentation comment for `declaration` refers to `OverloadedSymbol`, but no such entity seems to exist. – Randall Schulz Jan 17 '14 at 22:51
  • It turns out that that was happening 'cause I was calling it with `this.type` from a trait used as a supertype of the case class in question. – Randall Schulz Jan 18 '14 at 17:39
  • Also, `case class P(i: Int)(j: Int)`. – som-snytt Aug 26 '14 at 20:29