1

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.

Community
  • 1
  • 1
dirceusemighini
  • 1,344
  • 2
  • 16
  • 35
  • You can probably do it quite easily with macros, eg using shapeless `LabelledGeneric` – Cyrille Corpet Apr 04 '17 at 18:17
  • @CyrilleCorpet can you provide a link or an example? – dirceusemighini Apr 04 '17 at 18:31
  • Do you know that technically, an `Option` is another nested structure, with a field `x` in `Some`? Any recursive solution might make this appear in the result... – Cyrille Corpet Apr 04 '17 at 21:25
  • 1
    I was indeed able to derive an answer with shapeless (before the second update), using `LabelledTypeClassCompanion`, but it is not very easy to understand, and I'm not so sure it would be helpful. – Cyrille Corpet Apr 04 '17 at 21:26
  • @CyrilleCorpet I'm not yet confortable with scala reflection and macros, This is the second time that I need to use it and I couldn't figure this out by the docs. Is your answer based on macros? I think that if you post, and it work without the option, we can figure out how to the the type arg of the option by reflection – dirceusemighini Apr 04 '17 at 21:32

2 Answers2

4

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)
Jasper-M
  • 14,966
  • 2
  • 26
  • 37
0

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 traits and case classes:

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 classes, (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)).

Cyrille Corpet
  • 5,265
  • 1
  • 14
  • 31
  • Yeah, this seems a little complicated ;) But it's nice to get different ways of doing things. I couldn't call implicitly[RecursiveFields[A]].fields due to error: could not find implicit value for parameter e: RecursiveFields[A] – dirceusemighini Apr 05 '17 at 12:01