Last active
May 11, 2017 20:29
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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