获取有关Scala中案例类字段的完整信息

时间:2017-02-15 10:50:18

标签: scala reflection

考虑以下类和方法:

case class User(id: Long, name: String) {
  private var foo = "Foo" // shouldn't be printed
  val bar = "bar" // also shouldn't be printed
}
case class Message(id: Long, userId: Long, text: String)

def printInfo[E](o: E)(implicit tt: TypeTag[E]) = {

}

我想让这个方法打印任何案例类的每个字段的名称,类型和值,即

printInfo(User(1, "usr1")) // prints something like "(id, Long, 1), (name, String)"
printInfo(Message(1, 1, "Hello World")) // prints "(id, Long, 1), (userId, Long, 1), (text, String, "Hello World")"

为字段添加一些自定义注释也很可观。

2 个答案:

答案 0 :(得分:19)

您可以通过检查类型标记列出的成员并使用其镜像进行反映来完成此操作:

import scala.reflect.ClassTag
import scala.reflect.runtime.universe.TypeTag

def printInfo[A](a: A)(implicit tt: TypeTag[A], ct: ClassTag[A]): String = {
  val members = tt.tpe.members.collect {
    case m if m.isMethod && m.asMethod.isCaseAccessor => m.asMethod
  }

  members.map { member =>
    val memberValue = tt.mirror.reflect(a).reflectMethod(member)()
    s"(${ member.name }, ${ member.returnType }, $memberValue)"
  }.mkString(", ")
}

哪个会这样:

scala> case class User(id: Long, name: String) {
     |   private var foo = "Foo" // shouldn't be printed
     |   val bar = "bar" // also shouldn't be printed
     | }
defined class User

scala> case class Message(id: Long, userId: Long, text: String)
defined class Message

scala> printInfo(User(1, "usr1"))
res0: String = (name, String, usr1), (id, scala.Long, 1)

scala> printInfo(Message(1, 1, "Hello World"))
res1: String = (text, String, Hello World), (userId, scala.Long, 1), (id, scala.Long, 1)

(如果您想要Long代替scala.Long,那么从member.returnType获得的类型中删除前缀并不会太难,但我会这样做把它作为读者的练习。)

如果没有使用Shapeless进行任何运行时反射,也不难做到这一点:

import shapeless.{ ::, HList, HNil, LabelledGeneric, Typeable, Witness }
import shapeless.labelled.FieldType

trait PrettyPrintable[A] {
  def apply(a: A): List[(String, String, String)]
}

object PrettyPrintable {
  implicit val hnilPrettyPrintable: PrettyPrintable[HNil] =
    new PrettyPrintable[HNil] {
      def apply(a: HNil): List[(String, String, String)] = Nil
    }

  implicit def hconsPrettyPrintable[K <: Symbol, H, T <: HList](implicit
    kw: Witness.Aux[K],
    ht: Typeable[H],
    tp: PrettyPrintable[T]
  ): PrettyPrintable[FieldType[K, H] :: T] =
    new PrettyPrintable[FieldType[K, H] :: T] {
      def apply(a: FieldType[K, H] :: T): List[(String, String, String)] =
        (kw.value.name, ht.describe, a.head.toString) :: tp(a.tail)
    }

  implicit def genPrettyPrintable[A, R <: HList](implicit
    ag: LabelledGeneric.Aux[A, R],
    rp: PrettyPrintable[R]
  ): PrettyPrintable[A] = new PrettyPrintable[A] {
    def apply(a: A): List[(String, String, String)] = rp(ag.to(a))
  }

  def printInfo[A](a: A)(implicit pp: PrettyPrintable[A]) = pp(a).map {
    case (memberName, memberType, memberValue) =>
      s"($memberName, $memberType, $memberValue)"
  }.mkString(", ")
}

然后:

scala> PrettyPrintable.printInfo(User(1, "usr1"))
res2: String = (id, Long, 1), (name, String, usr1)

scala> PrettyPrintable.printInfo(Message(1, 1, "Hello World"))
res3: String = (id, Long, 1), (userId, Long, 1), (text, String, Hello World)

除此之外,它还为您提供了声明顺序中的字段,我认为应该可以使用类型标记方法,但我尽可能多地避免使用该API,因此我不知道如何做到这一点。不确定。

答案 1 :(得分:0)

现在为Scala 2.13case class(它们是Product的实现)提供了一种productElementNames方法,该方法返回其字段名称上的迭代器。

productIterator组合:

// val user = User(1, "user")
(user.productElementNames zip user.productIterator)
  .map { case (field, value) => (field, value.getClass.getSimpleName, value) }
  .toList
// List[(String, String, Any)] = List((id,Long,1), (name,String,user))

此:

  • 使用Iterator(id, name)

  • 提取字段名称(productElementNames
  • 使用Iterator(1, user)

  • 提取字段值(productIterator
  • zip的字段名称和字段值

  • 提取值的类名