Получить имя поля и значение из объекта класса case в scala во время выполнения

Пример класса case: пытался создать универсальный построитель запросов.

case class BaseQuery(operand: String, value: String)
case class ContactQuery(phone: BaseQuery, address: BaseQuery)
case class UserQuery(id: BaseQuery, name: BaseQuery, contact: ContactQuery)

val user = UserQuery(BaseQuery("equal","1"), BaseQuery("like","Foo"), ContactQuery(BaseQuery("eq","007-0000"),BaseQuery("like", "Foo City")))


//case class Contact(phone: String, address: String)

//case class User(id: Long, name: String, contact: Contact)

//val user = User(1, "Foo Dev", Contact("007-0000","Foo City"))

Как мы можем получить имена полей и соответствующие значения в scala,

При дальнейших исследованиях,

Решения с использованием Scala Reflection:

def classAccessors[T: TypeTag]: List[MethodSymbol] = typeOf[T].members.collect {
    case m: MethodSymbol if m.isCaseAccessor => m
}.toList

// The above snippet returns the field names, and as input we have to 
//feed the 'TypeTag' of the case class. And since we are only feeding a   
//TypeTag we will not have access to any object values. Is there any Scala
// Reflection variant of the solution.

Другое решение,

def getMapFromCaseClass(cc: AnyRef) =
(scala.collection.mutable.Map[String, Any]() /: cc.getClass.getDeclaredFields)
{
    (a, f) =>
        f.setAccessible(true)
        a + (f.getName -> f.get(cc))
}

// The above snippet returns a Map[String, Any] if we feed the case class 
//object. And we will need to match the map value to any of our other case 
//classes. If the class structure were simple, the above solution would be 
//helpful, but in case of complex case class this would not be the most efficient solution.

Попытка:

I am actually trying to retrieve the list of field and their values during runtime.

One of the use cases,

val list: Seq[Somehow(Field+Value)] = listingFieldWithValue(obj: UserQuery)
for(o <- list){
o.field.type match{
case typeOne: FooType =>{
//Do something
}
case typeTwo: FooBarType =>{
//Do something else
}
}

}

person mane    schedule 24.11.2015    source источник


Ответы (1)


Не совсем scala-reflect решение, но бесформенное решение. Shapeless построен на основе неявного макроса, поэтому он намного быстрее любого отражения, если у вас есть информация о типе во время компиляции.

Для реализации ваших обновленных требований вам потребуется некоторое время, чтобы внести необходимую информацию о типе.

sealed trait TypeInfo[T]{
  //include here thing you like to handle at runtime
}

Затем вы можете определить такую ​​информацию для разных типов и предоставить неявное разрешение. В моем примере такая реализация также подходит для последующего сопоставления

implicit case object StringField extends TypeInfo[String]

case class NumericField[N](implicit val num: Numeric[N]) extends TypeInfo[N]
implicit def numericField[N](implicit num: Numeric[N]): TypeInfo[N] = new NumericField[N]

case class ListField[E]() extends TypeInfo[List[E]]
implicit def listField[E] = new ListField[E]

Затем мы определяем более подходящую для сопоставления с образцом структуру результата

sealed trait FieldDef
case class PlainField[T](value: Any, info: TypeInfo[T]) extends FieldDef
case class EmbeddedType(values: Map[Symbol, FieldDef]) extends FieldDef

Таким образом, любой результат будет либо контейнером значений с необходимой информацией о типе, либо контейнером для более глубокого просмотра.

Наконец, мы можем определить реализацию концепции

import shapeless._
import shapeless.labelled.FieldType
import shapeless.ops.hlist.{ToTraversable, Mapper}

sealed abstract class ClassAccessors[C] {
  def apply(c: C): Map[Symbol, FieldDef]
}

trait LowPriorClassInfo extends Poly1 {
  implicit def fieldInfo[K <: Symbol, V](implicit witness: Witness.Aux[K], info: TypeInfo[V]) =
    at[FieldType[K, V]](f => (witness.value: Symbol, PlainField(f, info)))
}

object classInfo extends LowPriorClassInfo {
  implicit def recurseInfo[K <: Symbol, V](implicit witness: Witness.Aux[K], acc: ClassAccessors[V]) =
    at[FieldType[K, V]](f => (witness.value: Symbol, EmbeddedType(acc(f))))
}

implicit def provideClassAccessors[C, L <: HList, ML <: HList]
(implicit lgen: LabelledGeneric.Aux[C, L],
 map: Mapper.Aux[classInfo.type, L, ML],
 toList: ToTraversable.Aux[ML, List, (Symbol, FieldDef)]) =
  new ClassAccessors[C] {
    def apply(c: C) = toList(map(lgen.to(c))).toMap
  }

def classAccessors[C](c: C)(implicit acc: ClassAccessors[C]) = acc(c)

в настоящее время

classAccessors(User(100, "Miles Sabin", Contact("+1 234 567 890", "Earth, TypeLevel Inc., 10")))

приведет к

 Map(
 'id -> PlainField(100, NumericField( scala.math.Numeric$LongIsIntegral$@...)),
 'name -> PlainField("Miles Sabin", StringField), 
 'contact -> EmbeddedType( Map(
      'phone -> PlainField( "+1 234 567 890", StringField), 
      'address -> PlainField("Earth, TypeLevel Inc., 10", StringField))))
person Odomontois    schedule 24.11.2015
comment
не будет ли этот подход похож на второе решение, которое я опубликовал, где мне нужно будет сопоставить «Значение» [Символ, Значение-> Любое] с определенным классом? - person mane; 24.11.2015
comment
@mane На самом деле я адаптировал его к вашему второму случаю. Какой тип результата вам нужен? Поскольку shapeless имеет полную информацию о типе на уровне компиляции, его можно адаптировать ко всему. - person Odomontois; 24.11.2015
comment
Также, как вы можете видеть, он содержит встроенный объект Map, поэтому он выполняет полное рекурсивное преобразование. - person Odomontois; 24.11.2015
comment
@odmontois Я добавил вариант использования, я старался быть как можно более описательным, надеюсь, это помогло. Обновил вопрос. Я просто пытался опустить слой процесса, где мне нужно было бы сравнить «Любой» со списком классов случаев, которые у меня были бы. - person mane; 24.11.2015