如何获取 Scala 案例的字段和子字段列表 class

How to get list of fields and subfields of a Scala case class

我有一个嵌套的数据结构以防万一class,比如

Update2 所有值都是可选的

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)

我正在寻找一个正则表达式来跟踪 Class A 中的所有字段,直到它的所有子classes。

答案中的代码解决了我问题的第一步。它列出了第一级 (class A) 的所有字段。我试图将其更改为递归调用相同的方法,但我无法获取 MethodSymbol 的 TypeTag 信息。

我期望的结果是一个接收 A 作为参数的方法,returns

(b.id, b.name,c.cNode, c.cUser,d.dData, d.dFile)

如何从案例中获取子字段属性名称 class?

更新

我正在使用 scala 2.11

我也希望它由reflection/macros生成,因为数据结构很复杂,我希望它在案例class更新时更新。

您可以拨打methodSymbol.returnType。它会给你 return 类型的案例访问器,然后你可以递归地收集它的所有案例访问器。

这是一个完整的示例(假设每个字段都是 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)

重要提示

我认为这个答案不是一个好答案(我应 OP 的要求发布了它)。它涉及来自 shapeless 库的复杂结构,以避免处理宏或反射(实际上,它在幕后使用宏,但 shapeless 允许忘记它们)。


这是基于 shapeless Generic 宏。它涉及为您的数据类型创建一个类型类,并为任何具有无形 LabelledGeneric 的类型推断此类型类的实例,即具有 sealed traits 和 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)
  }
}

请注意,当您只处理 case class 时,不需要余积部分,(您可以将 LabelledTypeClass 替换为 LabelledProductTypeClassCompanion 也是如此).然而,由于 Option 是一个余积,这在我们的例子中是不正确的,但不清楚我们应该在那种情况下做出什么选择(我选择了在余积中第一个可能的选择,但这并不令人满意).

要使用它,只需调用 implicitly[RecursiveFields[A]].fields 来获取一个列表,其元素是所需的字段(在 . 上拆分,因此 b.name 实际上保持为 List(b, name)).