Skip to content

Instantly share code, notes, and snippets.

@trbngr
Created September 1, 2016 01:04
Show Gist options
  • Save trbngr/c9c6476ed230549e3918f2e88098f26d to your computer and use it in GitHub Desktop.
Save trbngr/c9c6476ed230549e3918f2e88098f26d to your computer and use it in GitHub Desktop.
Json4s Custom Serializer Macro
package com.company.macros
import scala.reflect.macros.blackbox
class ADTMacros(val c: blackbox.Context) {
import c.universe._
def customSerializer[T: c.WeakTypeTag]: Tree = {
generate[T] { (tpe, symbols) =>
val cases = symbols.map { symbol =>
val name = q"${symbol.name.decodedName.toString.trim}"
cq"""org.json4s.JsonAST.JString($name) => ${symbol.name.toTermName}"""
}
q"""new org.json4s.CustomSerializer[$tpe](f => ({case ..$cases}, {case x: $tpe => org.json4s.JsonAST.JString(x.toString)}))"""
}
}
private def generate[T: c.WeakTypeTag](func: (c.Type, List[c.Symbol]) => Tree): Tree = {
val t = weakTypeTag[T]
val (tpe, types) = t.tpe → collectKnownSubtypes(t.tpe.typeSymbol)
types match {
case Left(error) ⇒ reportErrors(error :: Nil)
case Right(values) => func(tpe, values)
}
}
private def collectKnownSubtypes(s: c.Symbol): Either[(c.Position, String), List[c.Symbol]] = {
if (s.isModule || s.isModuleClass)
Right(List(s))
else if (s.isClass) {
val cs = s.asClass
if ((cs.isTerm || cs.isAbstract) && cs.isSealed) {
cs.knownDirectSubclasses.foldLeft(Right(Nil): Either[(c.Position, String), List[Symbol]]) {
case (Left(error), _) => Left(error)
case (Right(set), knownSubclass) =>
collectKnownSubtypes(knownSubclass) match {
case Left(error) => Left(error)
case Right(subset) => Right(set ++ subset)
}
}
} else Left(cs.pos -> "Only sealed hierarchies or case objects are supported for ADT macros.")
} else Left(c.enclosingPosition -> "Only sealed hierarchies or case objects are supported for ADT macros.")
}
private def reportErrors(errors: Seq[(c.Position, String)]) = {
require(errors.nonEmpty)
val (lastPos, lastError) = errors.last
errors.dropRight(1).foreach { case (pos, error) ⇒ c.error(pos, error) }
c.abort(lastPos, lastError)
}
}
package com.company.serialization
import org.json4s.Serializer
import scala.language.experimental.macros
object SerializerMacros {
def serializerFromADT[T]: Serializer[T] = macro com.company.macros.ADTMacros.customSerializer[T]
}
sealed trait Thingy
case object RedThingy extends Thingy
case object BlueThingy extends Thingy
import com.company.serialization.SerializerMacros._
val thingySerializer = serializerFromADT[Thingy]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment