[info] Running com.github.fpopic.scalamacros.DefMacroUsage
Map(i -> 101, s -> Some(2), l -> List(3, 4), n -> Map(j -> 105, k -> 107))| // Would like to serialize any Scala case class (could be nested as well) to Map[String, Any] | |
| case class N(j: Int) | |
| case class A(i: Int, s: Some[Int], l: List[Int], n: N) | |
| // Want to generate: | |
| object N extends Mappable[N] { | |
| def toMap(n: N): Map[String, Any] = { | |
| Map( | |
| "j" -> n.j | |
| ) | |
| } | |
| } | |
| object A extends Mappable[A] { | |
| def toMap(a: A): Map[String, Any] = { | |
| Map( | |
| "i" -> a.i, | |
| "s" -> a.s, | |
| "l" -> a.l, | |
| "n" -> N.toMap(n) | |
| ) | |
| } | |
| } | |
| /* | |
| For later: | |
| - before adding pair to map check if null | |
| - convert Scala to Java collections | |
| - support Maps as fields | |
| - extract Some/Option | |
| - convert Timestamp-like fields to String Literals | |
| - add field customizations using annotations or some context (like customizationsMap) | |
| */ |
| package com.github.fpopic.scalamacros | |
| import scala.language.experimental.macros | |
| import scala.reflect.macros.blackbox | |
| // 0. Define Type Class | |
| trait Mappable[T] { | |
| def toMap(t: T): Map[String, Any] | |
| } | |
| object Mappable extends MappableLowPriorityImplicits { | |
| // 2. Implicit method that triggers the macro | |
| // HighPriorityImplicits | |
| // LowPriorityMacros | |
| implicit def caseClassMappable[T]: Mappable[T] = macro materializeMappableImpl[T] | |
| // 1. Caller initiates type class implicits resolution | |
| def mapify[T](t: T)(implicit m: Mappable[T]): Map[String, Any] = m.toMap(t) | |
| } | |
| trait MappableLowPriorityImplicits { | |
| // 3. Macro that generates for any case class Mappable implementation | |
| def materializeMappableImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[Mappable[T]] = { | |
| import c.universe._ | |
| val tpe: c.universe.Type = weakTypeOf[T] | |
| println(s"Mappable: $tpe") | |
| def getPrimaryConstructorMembers(_tpe: c.Type): Seq[c.Symbol] = | |
| _tpe.decls.collectFirst { | |
| case m: MethodSymbol if m.isPrimaryConstructor => m | |
| }.get.paramLists.head | |
| val mapEntries: Seq[c.Tree] = | |
| getPrimaryConstructorMembers(tpe).map { field => | |
| val fName = field.name.decodedName.toString | |
| val fTerm = field.name.toTermName | |
| // doesn't work with tType.decl(field.name).typeSignature | |
| val fType = field.typeSignature | |
| fType match { | |
| case t if t =:= weakTypeOf[Int] => | |
| println(s"$fName : $fType") | |
| q"$fName -> (t.$fTerm + 100)" | |
| case t if t =:= weakTypeOf[Some[Int]] => | |
| println(s"$fName : $fType") | |
| q"$fName -> t.$fTerm" | |
| case t if t =:= weakTypeOf[List[Int]] => | |
| println(s"$fName : $fType") | |
| q"$fName -> t.$fTerm" | |
| case n if n.baseClasses.contains(weakTypeOf[Product].typeSymbol) => | |
| println(s"$fName : $fType") | |
| q"$fName -> implicitly[Mappable[$n]].toMap(t.$fTerm)" | |
| case t => | |
| c.abort(c.enclosingPosition, s"Type $t not supported.") | |
| } | |
| } | |
| val ret = | |
| q"""new Mappable[$tpe] { | |
| def toMap(t: $tpe): Map[String, Any] = Map(..$mapEntries) | |
| } | |
| """ | |
| println(s"Ret: $ret") | |
| c.Expr[Mappable[T]](ret) | |
| } | |
| } |
| package com.github.fpopic.scalamacros | |
| case class N(j: Int, k: Int) | |
| case class A(i: Int, s: Some[Int], l: List[Int], n: N) | |
| object DefMacroUsage { | |
| def main(args: Array[String]): Unit = { | |
| import Mappable._ | |
| val a: Map[String, Any] = mapify( | |
| A( | |
| i = 1, | |
| s = Some(2), | |
| l = List(3, 4), | |
| n = N( | |
| j = 5, | |
| k = 7 | |
| ) | |
| ) | |
| ) | |
| println(a) | |
| } | |
| } |
| [info] Compiling 4 Scala sources to /home/fpopic/Projects/IdeaProjects/github/fpopic/scala-code-generation-poc/scala-macros-usage/target/scala-2.13.0-M3/classes ... | |
| Mappable: com.github.fpopic.scalamacros.A | |
| i : Int | |
| s : Some[Int] | |
| l : List[Int] | |
| n : com.github.fpopic.scalamacros.N | |
| Ret: { | |
| final class $anon extends Mappable[com.github.fpopic.scalamacros.A] { | |
| def <init>() = { | |
| super.<init>(); | |
| () | |
| }; | |
| def toMap(t: com.github.fpopic.scalamacros.A): Map[String, Any] = Map("i".$minus$greater(t.i.$plus(100)), "s".$minus$greater(t.s), "l".$minus$greater(t.l), "n".$minus$greater(implicitly[Mappable[com.github.fpopic.scalamacros.N]].toMap(t.n))) | |
| }; | |
| new $anon() | |
| } | |
| Mappable: com.github.fpopic.scalamacros.N | |
| j : Int | |
| k : Int | |
| Ret: { | |
| final class $anon extends Mappable[com.github.fpopic.scalamacros.N] { | |
| def <init>() = { | |
| super.<init>(); | |
| () | |
| }; | |
| def toMap(t: com.github.fpopic.scalamacros.N): Map[String, Any] = Map("j".$minus$greater(t.j.$plus(100)), "k".$minus$greater(t.k.$plus(100))) | |
| }; | |
| new $anon() | |
| } | |
| [info] Done compiling. | |
| [success] Total time: 18 s, completed Jun 1, 2019 11:39:14 PM |
[info] Running com.github.fpopic.scalamacros.DefMacroUsage
Map(i -> 101, s -> Some(2), l -> List(3, 4), n -> Map(j -> 105, k -> 107))