mane
mane

Reputation: 1169

Retrieve fieldname and value from case class object in scala, at runtime

An example case class: Was trying to create a generic query builder

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

How can we retrieve the field names and respective values in scala,

On further research,

Solutions using 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.

Another 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.

Trying to:

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
}
}

}

Upvotes: 1

Views: 2129

Answers (1)

Odomontois
Odomontois

Reputation: 16308

Not really scala-reflect solution, but shapeless one. Shapeless is built on top of implicit macro, so it way faster than any reflection, if you have type information at compile time.

For implementation of your updated requirements you'll need some time to carry needed type information

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

Next you can define such info for different types and provide implicit resolution. In my example such implementation are also suitable for later matching

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]

Then we define more suitable for pattern matching result structure

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

So any result will be either value container with needed type info or container for deeper look.

Finally we can define concept implementation

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)

now

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

will result to

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

Upvotes: 5

Related Questions