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)
).