From c5fe23594f66225eb18bf4e67472cd80023eb448 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Thu, 8 Mar 2018 16:53:44 -0800 Subject: Refactor build to use sbt-crossproject --- shared/src/main/scala/DerivedFormats.scala | 108 +++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 shared/src/main/scala/DerivedFormats.scala (limited to 'shared/src/main/scala/DerivedFormats.scala') diff --git a/shared/src/main/scala/DerivedFormats.scala b/shared/src/main/scala/DerivedFormats.scala new file mode 100644 index 0000000..d0cac38 --- /dev/null +++ b/shared/src/main/scala/DerivedFormats.scala @@ -0,0 +1,108 @@ +package spray.json + +import magnolia._ + +import scala.language.experimental.macros + +/** Mixin that enables derivation of JSON formats for any product + * (case classes) or coproduct (sealed traits) types. */ +trait DerivedFormats { self: BasicFormats => + type Typeclass[T] = JsonFormat[T] + + def combine[T](ctx: CaseClass[JsonFormat, T]): JsonFormat[T] = + new JsonFormat[T] { + override def write(value: T): JsValue = { + val fields: Seq[(String, JsValue)] = ctx.parameters.map { param => + param.label -> param.typeclass.write(param.dereference(value)) + } + JsObject(fields: _*) + } + + override def read(value: JsValue): T = value match { + case obj: JsObject => + if (ctx.isObject) { + ctx.rawConstruct(Seq.empty) + } else { + ctx.construct { param => + param.typeclass.read(obj.fields(param.label)) + } + } + case js => + deserializationError( + s"Cannot read JSON '$js' as a ${ctx.typeName.full}") + } + } + + def dispatch[T](ctx: SealedTrait[JsonFormat, T]): JsonFormat[T] = { + val typeFieldName = ctx.annotations + .collectFirst { + case g: gadt => g.typeFieldName + } + .getOrElse("type") + + new JsonFormat[T] { + override def write(value: T): JsValue = ctx.dispatch(value) { sub => + sub.typeclass.write(sub.cast(value)) match { + case obj: JsObject => + JsObject( + (Map(typeFieldName -> JsString(sub.typeName.short)) ++ + obj.fields).toSeq: _*) + case js => js + } + } + + override def read(js: JsValue): T = { + val typeName: String = js match { + case obj: JsObject if obj.fields.contains(typeFieldName) => + obj.fields(typeFieldName).convertTo[String] + case JsString(str) => + str + case _ => + deserializationError( + s"Cannot deserialize JSON to ${ctx.typeName.full} " + + "because serialized type cannot be determined.") + } + + ctx.subtypes.find(_.typeName.short == typeName) match { + case Some(tpe) => tpe.typeclass.read(js) + case None => + deserializationError( + s"Cannot deserialize JSON to ${ctx.typeName.full} " + + s"because type '${typeName}' is unsupported.") + } + } + } + } + + def jsonFormat[T]: RootJsonFormat[T] = + macro DerivedFormatMacros.derivedFormat[T] + +} +object DerivedFormats extends DerivedFormats with DefaultJsonProtocol + +trait ImplicitDerivedFormats extends DerivedFormats { self: BasicFormats => + implicit def implicitJsonFormat[T]: RootJsonFormat[T] = + macro DerivedFormatMacros.derivedFormat[T] +} +object ImplicitDerivedFormats + extends ImplicitDerivedFormats + with DefaultJsonProtocol + +object DerivedFormatMacros { + import scala.reflect.macros.whitebox._ + + /** Utility that converts a magnolia-generated JsonFormat to a RootJsonFormat. */ + def derivedFormat[T: c.WeakTypeTag](c: Context): c.Tree = { + import c.universe._ + val tpe = weakTypeOf[T].typeSymbol.asType + val sprayPkg = c.mirror.staticPackage("spray.json") + val valName = TermName(c.freshName("format")) + q"""{ + val $valName = ${Magnolia.gen[T](c)} + new $sprayPkg.RootJsonFormat[$tpe] { + def write(value: $tpe) = $valName.write(value) + def read(value: $sprayPkg.JsValue) = $valName.read(value) + } + }""" + } +} -- cgit v1.2.3