I define following macro to transform case fields to map
import scala.language.experimental.macros
import scala.reflect.macros.Context
def asMap_impl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T]) = {
import c.universe._
val mapApply = Select(reify(Map).tree, newTermName("apply"))
val pairs = weakTypeOf[T].declarations.collect {
case m: MethodSymbol if m.isCaseAccessor =>
val name = c.literal(m.name.decoded)
val value = c.Expr(Select(t.tree, m.name))
reify(name.splice -> value.splice).tree
}
c.Expr[Map[String, Any]](Apply(mapApply, pairs.toList))
}
And the method implementation
def asMap[T](t: T) = macro asMap_impl[T]
Then I define a case class to test it
case class User(name : String)
It works fine(with scala repl):
scala> asMap(User("foo")) res0:
scala.collection.immutable.Map[String,String] = Map(name -> foo)
But When I wrap this method with another generic method
def printlnMap[T](t: T) = println(asMap(t))
This method always print empty map:
scala> printlnMap(User("foo"))
Map()
The type information seems lost, how to get the printlnMap to print all fields ?
The reason why this doesn't work is that your macro will be called only once - when compiling printlnMap function. This way it will see T as an abstract type. The macro will not be called on each invocation of printlnMap.
One way to quickly fix this is to implement printlnMap also as a macro. Of course, this is not ideal. So, here's a different approach - materialization of typeclass instances:
First, define a typeclass that will allow us to convert case class instances to maps:
trait CaseClassToMap[T] {
def asMap(t: T): Map[String,Any]
}
Then, implement a macro that will materialize an instance of this type class for some case class T. You can put it into CaseClassToMap companion object so that it is visible globally.
object CaseClassToMap {
implicit def materializeCaseClassToMap[T]: CaseClassToMap[T] = macro impl[T]
def impl[T: c.WeakTypeTag](c: Context): c.Expr[CaseClassToMap[T]] = {
import c.universe._
val mapApply = Select(reify(Map).tree, newTermName("apply"))
val pairs = weakTypeOf[T].declarations.collect {
case m: MethodSymbol if m.isCaseAccessor =>
val name = c.literal(m.name.decoded)
val value = c.Expr(Select(Ident(newTermName("t")), m.name))
reify(name.splice -> value.splice).tree
}
val mapExpr = c.Expr[Map[String, Any]](Apply(mapApply, pairs.toList))
reify {
new CaseClassToMap[T] {
def asMap(t: T) = mapExpr.splice
}
}
}
}
Now, you can do this:
def asMap[T: CaseClassToMap](t: T) =
implicitly[CaseClassToMap[T]].asMap(t)
def printlnMap[T: CaseClassToMap](t: T) =
println(asMap(t))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With