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

时间:2017-04-04 17:58:56

标签: scala reflection case-class

我在案例类中有一个嵌套的数据结构,比如

Update2 所有的val都是可选的

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)

我正在寻找一个正则表达式来跟踪A类中的所有字段,以及它所有的子类。

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

我期待的结果是一个接收A作为参数的方法,并返回

  

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

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

更新

我正在使用scala 2.11

我还希望它由反射/宏生成,因为数据结构很复杂,我希望在更新案例类时更新它。

2 个答案:

答案 0 :(得分:4)

您可以致电methodSymbol.returnType。它将为您提供案例访问器的返回类型,然后您可以递归地收集它的所有案例访问器。

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

答案 1 :(得分:0)

重要提示

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

这是基于shapeless Generic个宏。它涉及为您的数据类型创建一个类型类,并为任何具有LabelledGeneric无形状的类型推断此类型类的实例,即具有sealed traitcase class的数据结构:

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 es时,不需要联合产品部分(您可以将LabelledTypeClass替换为LabelledProductTypeClass,将Companion替换为Option。但是,由于implicitly[RecursiveFields[A]].fields是副产品,在我们的案例中并非如此,但我不清楚在这种情况下我们应该做出什么选择(我选择在副产品中采取第一种可能的选择,但这并不令人满意)。

要使用此功能,只需致电.即可获取其元素为所需字段的列表(在b.name上拆分,以便List(b, name)实际保留为<script src="https://code.jquery.com/jquery-3.2.1.js"></script> <i id="xyz" class="class1" >hey</i> <div id="abc" class="class2" style="display: block;">lo</div> <script type="text/javascript> $(function() { $(".xyz").click(function() { console.log("element with class xyz was clicked"); $(".abc").css('display': 'none'); }); }); </script> ) 。