Reputation: 1354
I have a nested data structure in case classes, like
Update2 All vals are optional
case class A(b:Option[B] = None,c:Option[C] = None,d:Option[D] = None)
case class B(id:Option[String] = None, name:Option[String] = None)
case class C(cNode:Option[String] = None, cuser:Option[String] = None)
case class D(dData:Option[String] = None, dField:Option[String] = None)
I'm looking for a regex to track all fields from the Class A, through all of it's subclasses.
The code from this answer solve the first step of my problem. It list all of the fields of the first level (class A). I tried to change it to recursive call the same method but I can't get the TypeTag information of a MethodSymbol.
The result I'm expecting for this is a method that receives A as argument, and returns
(b.id, b.name,c.cNode, c.cUser,d.dData, d.dFile)
How can I get the subfields attribute names from a case class?
UPDATE
I'm using scala 2.11
I also want it to be generated by reflection/macros because the data structure is complex and I want it to be updated when the case class are updated.
Upvotes: 1
Views: 2667
Reputation: 15086
You can call methodSymbol.returnType
. It will give you the return type of the case accessor on which you can then recursively collect all its case accessors.
Here is a full example (assuming that every field is an Option
):
scala> :paste
// Entering paste mode (ctrl-D to finish)
case class A(b:Option[B] = None,c:Option[C] = None,d:Option[D] = None)
case class B(id:Option[String] = None, name:Option[String] = None)
case class C(cNode:Option[String] = None, cuser:Option[String] = None)
case class D(dData:Option[String] = None, dField:Option[String] = None)
import scala.reflect.runtime.universe._
def allFields[T: TypeTag]: List[String] = {
def rec(tpe: Type): List[List[Name]] = {
val collected = tpe.members.collect {
case m: MethodSymbol if m.isCaseAccessor => m
}.toList
if (collected.nonEmpty)
collected.flatMap(m => rec(m.returnType.typeArgs.head).map(m.name :: _))
else
List(Nil)
}
rec(typeOf[T]).map(_.mkString("."))
}
// Exiting paste mode, now interpreting.
scala> allFields[A]
res0: List[String] = List(d.dField, d.dData, c.cuser, c.cNode, b.name, b.id)
Upvotes: 4
Reputation: 5315
Important note
This answer is not a good one in my opinion (I posted it on OP's request). It involves complicated structures from shapeless
library to avoid dealing with macros or reflection (actually, it uses macros under the hood, but shapeless
allows to forget about them).
This is based on shapeless
Generic
macros. It involves creating a typeclass for your data type, and infering the instances of this typeclass for any type which has a LabelledGeneric
in shapeless, ie a data structure with sealed trait
s and case class
es:
import shapeless.{:+:, CNil, Coproduct, HList, HNil, LabelledTypeClass, LabelledTypeClassCompanion}
trait RecursiveFields[T] {
def fields: List[List[String]]
override def toString = fields.map(_.mkString(".")).mkString("(", ", ", ")")
}
object RecursiveFields extends LabelledTypeClassCompanion[RecursiveFields] {
def apply[T](f: List[List[String]]): RecursiveFields[T] = new RecursiveFields[T] {
val fields = f
}
implicit val string: RecursiveFields[String] = apply[String](Nil)
implicit def anyVal[T <: AnyVal]: RecursiveFields[T] = apply[T](Nil)
object typeClass extends LabelledTypeClass[RecursiveFields] {
override def product[H, T <: HList](name: String, ch: RecursiveFields[H], ct: RecursiveFields[T]): RecursiveFields[shapeless.::[H, T]] =
RecursiveFields{
val hFields = if (ch.fields.isEmpty) List(List(name)) else ch.fields.map(name :: _)
hFields ++ ct.fields
}
override def emptyProduct: RecursiveFields[HNil] = RecursiveFields(Nil)
override def project[F, G](instance: => RecursiveFields[G], to: (F) => G, from: (G) => F): RecursiveFields[F] =
RecursiveFields(instance.fields)
override def coproduct[L, R <: Coproduct](name: String, cl: => RecursiveFields[L], cr: => RecursiveFields[R]): RecursiveFields[:+:[L, R]] =
RecursiveFields[L :+: R](product(name, cl, emptyProduct).fields)
override def emptyCoproduct: RecursiveFields[CNil] = RecursiveFields(Nil)
}
}
Note that the coproduct part is not necessary while you only deal with case class
es, (you may replace LabelledTypeClass
with LabelledProductTypeClass
, and the same for Companion
). However, since Option
is a coproduct, this is not true in our case, but it is unclear what choice we should make in that case (I chose to take the first possible choice in the coproduct, but this is not satisfactory).
To use this, just call implicitly[RecursiveFields[A]].fields
to get a list whose elements are the desired fields (splitted on .
, so that b.name
is actually kept as List(b, name)
).
Upvotes: 0