import scala.reflect.macros.Context import scala.language.experimental.macros trait MapReader[T] { // should really return Option[T], but let's keep it simple in this example def read(map: Map[String, Any], key: String): T } object MapReader extends MapReaderLowPriorityImplicits { // convenience method to retrieve implicit instance explicitly def apply[T: MapReader]: MapReader[T] = implicitly[MapReader[T]] // these implicits are always implicitly available as long as `MapReader` is in scope implicit object IntMapReader extends MapReader[Int] { def read(map: Map[String, Any], key: String): Int = map(key).asInstanceOf[Int] } implicit object StringMapReader extends MapReader[String] { def read(map: Map[String, Any], key: String): String = map(key).toString } implicit object DoubleMapReader extends MapReader[Double] { def read(map: Map[String, Any], key: String): Double = { map(key).asInstanceOf[Double] } } // etc. for all the basic types implicit def caseClassMapReader[T]: MapReader[T] = macro caseClassMapReaderImpl[T] } // these are only attempted if none of the implicits in the companion object work trait MapReaderLowPriorityImplicits { def caseClassMapReaderImpl[T: c.WeakTypeTag](c: Context): c.Expr[MapReader[T]] = { import c.universe._ def recurseParams(tpe:c.universe.Type, keys:List[String] = Nil):List[Tree] = { val fields = tpe.decls.collectFirst{ case m:MethodSymbol if m.isPrimaryConstructor => m }.get.paramLists.head fields.map { field => val name = field.name val decoded = name.decoded val returnType = tpe.declaration(name).typeSignature returnType match { case x if x.baseClasses.contains(typeOf[Product].typeSymbol) => q"${x.typeSymbol.companion}(..${recurseParams(x, keys :+ decoded)})" case x => val key2Get = keys.foldLeft[Tree](q"map")((prv, next) => q"$prv(${next}).asInstanceOf[Map[String, Any]]") q"MapReader[$returnType].read($key2Get, $decoded)" } } } val tpe = weakTypeOf[T] val companion = tpe.typeSymbol.companionSymbol val output = q""" new MapReader[$tpe] { def read(map: Map[String, Any], key: String): $tpe = $companion(..${recurseParams(tpe)}) } """ println(output) c.Expr[MapReader[T]] { output } } }