Created
August 26, 2017 22:24
-
-
Save jodersky/8b5c1dddb92a2bd7d4895e94494b9420 to your computer and use it in GitHub Desktop.
Spray-JSON formats for product types, automatically provided with the use of macros.
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
package spray.json | |
import scala.language.experimental.macros | |
import scala.reflect.macros.blackbox.Context | |
trait MacroFormats { | |
def macroReader[A]: RootJsonReader[A] = macro MacroFormatsBundle.jsonReader[A] | |
def macroWriter[A]: RootJsonWriter[A] = macro MacroFormatsBundle.jsonWriter[A] | |
def macroFormat[A]: RootJsonFormat[A] = macro MacroFormatsBundle.jsonFormat[A] | |
} | |
object MacroFormats extends MacroFormats | |
private[json] class MacroFormatsBundle(val c: Context) { | |
import c.universe._ | |
private def checkProduct[A: c.WeakTypeTag]: Type = { | |
val product = weakTypeOf[A] | |
if (!(product <:< weakTypeOf[Product])) { | |
c.abort(c.enclosingPosition, | |
s"Cannot generate reader for non-product type $product") | |
} | |
product | |
} | |
/** Summon an implicit value and return the tree representing its | |
* invocation, or fail if no implicit is available. */ | |
private def implicitlyOrFail(tpe: Type, message: String): Tree = { | |
c.typecheck( | |
q"""{ | |
import ${c.prefix}._ | |
implicitly[$tpe] | |
}""", | |
silent = true | |
) match { | |
case EmptyTree => c.abort(c.enclosingPosition, message) | |
case tree => tree | |
} | |
} | |
def jsonReader[A: c.WeakTypeTag]: c.Tree = { | |
val product = checkProduct[A] | |
val fields: Map[TermName, Type] = product.decls.collect { | |
case m: MethodSymbol if m.isCaseAccessor => | |
m.name -> m.info.resultType | |
}.toMap | |
val readers: Map[TermName, Tree] = fields.map { | |
case (name, tpe) => | |
name -> implicitlyOrFail( | |
appliedType(typeOf[JsonReader[_]], tpe), | |
s"No implicit reader available for $product.$name of type $tpe") | |
} | |
val mapReaderType = typeOf[JsonReader[Map[String, JsValue]]] | |
val mapReader = implicitlyOrFail( | |
mapReaderType, | |
s"No implicit reader available for ${mapReaderType}." | |
) | |
q"""new _root_.spray.json.RootJsonReader[$product]{ | |
override def read(js: spray.json.JsValue): $product = { | |
val fields = $mapReader.read(js) | |
new $product( | |
..${readers.map { | |
case (name, reader) => | |
q"$name = $reader.read(fields(${name.toString}))" | |
}} | |
) | |
} | |
}""" | |
} | |
def jsonWriter[A: c.WeakTypeTag]: c.Tree = { | |
val product = checkProduct[A] | |
val fields: Map[TermName, Type] = product.decls.collect { | |
case m: MethodSymbol if m.isCaseAccessor => | |
m.name -> m.info.resultType | |
}.toMap | |
val writers: Map[TermName, Tree] = fields.map { | |
case (name, tpe) => | |
name -> implicitlyOrFail( | |
appliedType(typeOf[JsonWriter[_]], tpe), | |
s"No implicit writer available for $product.$name of type $tpe") | |
} | |
val mapWriterType = typeOf[JsonWriter[Map[String, JsValue]]] | |
val mapWriter = implicitlyOrFail( | |
mapWriterType, | |
s"No implicit writer available for ${mapWriterType}." | |
) | |
q"""new _root_.spray.json.RootJsonWriter[$product]{ | |
override def write(cc: $product): _root_.spray.json.JsValue = { | |
val fields = _root_.scala.collection.immutable.Map( | |
..${writers.map { | |
case (name, writer) => | |
q"${name.toString} -> $writer.write(cc.$name)" | |
}} | |
) | |
$mapWriter.write(fields) | |
} | |
}""" | |
} | |
def jsonFormat[A: c.WeakTypeTag]: c.Tree = { | |
val product = checkProduct[A] | |
q"""{ | |
val reader = macroReader[$product] | |
val writer = macroWriter[$product] | |
new _root_.spray.json.RootJsonFormat[$product]{ | |
override def read(js: spray.json.JsValue) = reader.read(js) | |
override def write(cc: $product) = writer.write(cc) | |
} | |
}""" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment