2017-02-15 3 views
10

Рассмотрим следующий класс и метод:Получение полной информации о полях класса дела в Scala

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")" 

Добавление некоторых пользовательских аннотаций для полей также является значительным.

+1

Что цель? Что вы уже пробовали? – cchantep

+0

Это почти дубликат вашего [предыдущего вопроса] (http://stackoverflow.com/questions/42189259/obtaining-class-name-and-property-names-from-generic-class). –

ответ

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, но я оставлю это как упражнение для читателя.)

Это также не слишком трудно сделать это без выполнения отражения, используя бесформенный:

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 так часто, как я могу, поэтому с головы до головы я не уверен.

Смежные вопросы