如何获取 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 trait
s 和 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)
}
}
请注意,当您只处理 case class
时,不需要余积部分,(您可以将 LabelledTypeClass
替换为 LabelledProductTypeClass
,Companion
也是如此).然而,由于 Option
是一个余积,这在我们的例子中是不正确的,但不清楚我们应该在那种情况下做出什么选择(我选择了在余积中第一个可能的选择,但这并不令人满意).
要使用它,只需调用 implicitly[RecursiveFields[A]].fields
来获取一个列表,其元素是所需的字段(在 .
上拆分,因此 b.name
实际上保持为 List(b, name)
).
我有一个嵌套的数据结构以防万一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。
我期望的结果是一个接收 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 trait
s 和 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)
}
}
请注意,当您只处理 case class
时,不需要余积部分,(您可以将 LabelledTypeClass
替换为 LabelledProductTypeClass
,Companion
也是如此).然而,由于 Option
是一个余积,这在我们的例子中是不正确的,但不清楚我们应该在那种情况下做出什么选择(我选择了在余积中第一个可能的选择,但这并不令人满意).
要使用它,只需调用 implicitly[RecursiveFields[A]].fields
来获取一个列表,其元素是所需的字段(在 .
上拆分,因此 b.name
实际上保持为 List(b, name)
).