Skip to content

Instantly share code, notes, and snippets.

@lukestewart13
Last active May 11, 2017 20:29
Show Gist options
  • Save lukestewart13/de7e38528b7975cec2ede62618eb6ba1 to your computer and use it in GitHub Desktop.
Save lukestewart13/de7e38528b7975cec2ede62618eb6ba1 to your computer and use it in GitHub Desktop.
Macro annotation to generate Play default JSON reads/writes in a case class companion object
import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
class JsonReads extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro JsonConversion.readsImpl
}
class JsonWrites extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro JsonConversion.writesImpl
}
class JsonFormat extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro JsonConversion.readWriteImpl
}
object JsonConversion {
sealed trait ConversionType
case object ReadConversion extends ConversionType
case object WriteConversion extends ConversionType
case object ReadWriteConversion extends ConversionType
def readsImpl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = impl(c)(annottees: _*)(ReadConversion)
def writesImpl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = impl(c)(annottees: _*)(WriteConversion)
def readWriteImpl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = impl(c)(annottees: _*)(ReadWriteConversion)
private def impl(c: whitebox.Context)(annottees: c.Expr[Any]*)(conversionType: ConversionType): c.Expr[Any] = {
import c.universe._
def extractCaseClassParts(classDecl: ClassDef) = classDecl match {
case q"@..$annots case class $className [ ..$tpes ] (..$fields) extends ..$parents { ..$body }" =>
(annots, className, tpes, fields, parents, body)
}
def extractCompanionObjectParts(objDef: ModuleDef) = objDef match {
case q"object $className extends ..$parents { ..$body }" => (className, parents, body)
}
//@todo get working for generic types
def extractConverter(className: TypeName, typeParameters: Seq[TypeDef], conversionType: ConversionType) = conversionType match {
case ReadConversion =>
q"""implicit val reads: play.api.libs.json.Reads[$className[..$typeParameters]] = play.api.libs.json.Json.reads[$className[..$typeParameters]]"""
case WriteConversion =>
q"""implicit val writes: play.api.libs.json.Writes[$className[..$typeParameters]] = play.api.libs.json.Json.writes[$className[..$typeParameters]]"""
case ReadWriteConversion =>
q"""implicit val format: play.api.libs.json.Format[$className[..$typeParameters]] = play.api.libs.json.Json.format[$className[..$typeParameters]]"""
}
def modifiedDeclaration(classDecl: ClassDef, optCompanionDecl: Option[ModuleDef]) = {
val (_, className, tpes, _, _, _) = extractCaseClassParts(classDecl)
val converter = extractConverter(className.asInstanceOf[TypeName], tpes.asInstanceOf[Seq[TypeDef]], conversionType)
optCompanionDecl match {
case Some(companionDecl) =>
val (compName, compParents, compBody) = extractCompanionObjectParts(companionDecl)
c.Expr[Any](
q"""$classDecl; object $compName extends ..$compParents {
$converter
..$compBody
}
"""
)
case None =>
val compName = className.asInstanceOf[TypeName].toTermName
c.Expr[Any](
q"""$classDecl; object $compName {
$converter
}
"""
)
}
}
annottees.map(_.tree).toList match {
case List(classDecl: ClassDef) => modifiedDeclaration(classDecl, None)
case List(classDecl: ClassDef, companionDecl: ModuleDef) => modifiedDeclaration(classDecl, Some(companionDecl))
case _ => c.abort(c.enclosingPosition, "Invalid annottee")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment