aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJakob Odersky <jakob@odersky.com>2018-03-08 16:53:44 -0800
committerJakob Odersky <jakob@odersky.com>2018-03-28 10:34:31 -0700
commitc5fe23594f66225eb18bf4e67472cd80023eb448 (patch)
tree58fe8509b3990932134c18e09ed69ee83b779373 /src
parent2f3f9aa851080b6c79f3af14b3fdfeab56feffec (diff)
downloadspray-json-derivation-c5fe23594f66225eb18bf4e67472cd80023eb448.tar.gz
spray-json-derivation-c5fe23594f66225eb18bf4e67472cd80023eb448.tar.bz2
spray-json-derivation-c5fe23594f66225eb18bf4e67472cd80023eb448.zip
Refactor build to use sbt-crossproject
Diffstat (limited to 'src')
-rw-r--r--src/main/scala/DerivedFormats.scala108
-rw-r--r--src/main/scala/annotations.scala22
-rw-r--r--src/test/scala/CoproductTypeFormatTests.scala79
-rw-r--r--src/test/scala/FormatTests.scala19
-rw-r--r--src/test/scala/ImplicitDerivedFormatTests.scala24
-rw-r--r--src/test/scala/ProductTypeFormatTests.scala77
6 files changed, 0 insertions, 329 deletions
diff --git a/src/main/scala/DerivedFormats.scala b/src/main/scala/DerivedFormats.scala
deleted file mode 100644
index d0cac38..0000000
--- a/src/main/scala/DerivedFormats.scala
+++ /dev/null
@@ -1,108 +0,0 @@
-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)
- }
- }"""
- }
-}
diff --git a/src/main/scala/annotations.scala b/src/main/scala/annotations.scala
deleted file mode 100644
index 9d35d16..0000000
--- a/src/main/scala/annotations.scala
+++ /dev/null
@@ -1,22 +0,0 @@
-package spray.json
-
-import scala.annotation.StaticAnnotation
-
-/** An annotation that designates that a sealed trait is a generalized algebraic
- * datatype (GADT), and that a type field containing the serialized childrens'
- * types should be added to the final JSON objects.
- *
- * Note that by default all sealed traits are treated as GADTs, with a type
- * field called `type`. This annotation enables overriding the name of that
- * field and is really only useful if a child itself has a field called `type`
- * that would result in a conflict.
- *
- * Example:
- * {{{
- * // the JSON field "kind" will contain the actual type of the serialized child
- * @gadt("kind") sealed abstract class Keyword(`type`: String)
- * case class If(`type`: String) extends Keyword(`type`)
- * }}}
- * @param typeFieldName the name of the field to inject into a serialized JSON
- * object */
-final class gadt(val typeFieldName: String = "type") extends StaticAnnotation
diff --git a/src/test/scala/CoproductTypeFormatTests.scala b/src/test/scala/CoproductTypeFormatTests.scala
deleted file mode 100644
index de73967..0000000
--- a/src/test/scala/CoproductTypeFormatTests.scala
+++ /dev/null
@@ -1,79 +0,0 @@
-package spray.json
-
-import org.scalatest._
-
-class CoproductTypeFormatTests
- extends FlatSpec
- with FormatTests
- with DefaultJsonProtocol
- with DerivedFormats {
-
- sealed trait Expr
- case class Zero() extends Expr
- case class Value(x: Int) extends Expr
- case class Plus(lhs: Expr, rhs: Expr) extends Expr
- case object One extends Expr
-
- implicit val exprFormat: RootJsonFormat[Expr] = jsonFormat[Expr]
-
- "No-parameter case class child" should behave like checkRoundtrip[Expr](
- Zero(),
- """{"type":"Zero"}"""
- )
-
- "Simple parameter case class child" should behave like checkRoundtrip[Expr](
- Value(42),
- """{"type":"Value","x":42}"""
- )
-
- "Nested parameter case class child" should behave like checkRoundtrip[Expr](
- Plus(Value(42), One),
- """{"type":"Plus","lhs":{"type":"Value","x":42},"rhs":{"type":"One"}}"""
- )
-
- "Case object child" should behave like checkRoundtrip[Expr](
- One,
- """{"type": "One"}"""
- )
-
- @gadt("kind")
- sealed abstract class Keyword(`type`: String)
- case class If(`type`: String) extends Keyword(`type`)
-
- implicit val keywordFormat: RootJsonFormat[Keyword] = jsonFormat[Keyword]
-
- "GADT with type field alias" should behave like checkRoundtrip[Keyword](
- If("class"),
- """{"kind":"If","type":"class"}"""
- )
-
- @gadt("""_`crazy type!`"""")
- sealed abstract trait Crazy
- case class CrazyType() extends Crazy
-
- implicit val crazyFormat: RootJsonFormat[Crazy] = jsonFormat[Crazy]
-
- "GADT with special characters in type field" should behave like checkRoundtrip[
- Crazy](
- CrazyType(),
- """{"_`crazy type!`\"": "CrazyType"}"""
- )
-
- sealed trait Enum
- case object A extends Enum
- case object B extends Enum
-
- implicit val enumFormat: RootJsonFormat[Enum] = jsonFormat[Enum]
-
- "Enum" should behave like checkRoundtrip[List[Enum]](
- A :: B :: Nil,
- """[{"type":"A"}, {"type":"B"}]"""
- )
-
- "Serializing as sealed trait and deserializing as child" should "work" in {
- implicit val plusFormat: RootJsonFormat[Plus] = jsonFormat[Plus]
- val expr: Expr = Plus(Value(42), Plus(Zero(), One))
- assert(expr.toJson.convertTo[Plus] == expr)
- }
-
-}
diff --git a/src/test/scala/FormatTests.scala b/src/test/scala/FormatTests.scala
deleted file mode 100644
index 0f56091..0000000
--- a/src/test/scala/FormatTests.scala
+++ /dev/null
@@ -1,19 +0,0 @@
-package spray.json
-
-import org.scalatest._
-
-trait FormatTests { self: FlatSpec =>
-
- def checkRoundtrip[A: RootJsonFormat](a: A, expectedJson: String) = {
- it should "serialize to the expected JSON value" in {
- val expected: JsValue = expectedJson.parseJson
- assert(a.toJson == expected)
- }
-
- it should "serialize then deserialize back to itself" in {
- val reread = a.toJson.convertTo[A]
- assert(reread == a)
- }
- }
-
-}
diff --git a/src/test/scala/ImplicitDerivedFormatTests.scala b/src/test/scala/ImplicitDerivedFormatTests.scala
deleted file mode 100644
index 1d54ae9..0000000
--- a/src/test/scala/ImplicitDerivedFormatTests.scala
+++ /dev/null
@@ -1,24 +0,0 @@
-package spray.json
-
-import org.scalatest._
-
-class ImplicitDerivedFormatTests
- extends FlatSpec
- with FormatTests
- with ImplicitDerivedFormats
- with DefaultJsonProtocol {
-
- case class B(x: Int, b: String, mp: Map[String, Int])
- case class C(b: B)
-
- "Simple parameter product" should behave like checkRoundtrip(
- B(42, "Hello World", Map("a" -> 1, "b" -> -1024)),
- """{ "x": 42, "b": "Hello World", "mp": { "a": 1, "b": -1024 } }"""
- )
-
- "Nested parameter product with custom child format" should behave like checkRoundtrip(
- C(B(42, "Hello World", Map("a" -> 1, "b" -> -1024))),
- """{"b" :{ "x": 42, "b": "Hello World", "mp": { "a": 1, "b": -1024 } } }"""
- )
-
-}
diff --git a/src/test/scala/ProductTypeFormatTests.scala b/src/test/scala/ProductTypeFormatTests.scala
deleted file mode 100644
index ce05000..0000000
--- a/src/test/scala/ProductTypeFormatTests.scala
+++ /dev/null
@@ -1,77 +0,0 @@
-package spray.json
-
-import org.scalatest._
-
-class ProductTypeFormatTests
- extends FlatSpec
- with FormatTests
- with DerivedFormats
- with DefaultJsonProtocol {
-
- case class A()
- case class B(x: Int, b: String, mp: Map[String, Int])
- case class C(b: B)
- case object D
- case class E(d: D.type)
- case class F(x: Int)
- case class G(f: F)
-
- implicit val aFormat: RootJsonFormat[A] = jsonFormat[A]
- implicit val bFormat: RootJsonFormat[B] = jsonFormat[B]
- implicit val cFormat: RootJsonFormat[C] = jsonFormat[C]
- implicit val dFormat: RootJsonFormat[D.type] = jsonFormat[D.type]
- implicit val eFormat: RootJsonFormat[E] = jsonFormat[E]
-
- "No-parameter product" should behave like checkRoundtrip(A(), "{}")
-
- "Simple parameter product" should behave like checkRoundtrip(
- B(42, "Hello World", Map("a" -> 1, "b" -> -1024)),
- """{ "x": 42, "b": "Hello World", "mp": { "a": 1, "b": -1024 } }"""
- )
-
- "Nested parameter product" should behave like checkRoundtrip(
- C(B(42, "Hello World", Map("a" -> 1, "b" -> -1024))),
- """{"b" :{ "x": 42, "b": "Hello World", "mp": { "a": 1, "b": -1024 } } }"""
- )
-
- "Case object" should behave like checkRoundtrip(
- D,
- "{}"
- )
-
- "Case object as parameter" should behave like checkRoundtrip(
- E(D),
- """{"d":{}}"""
- )
-
- // custom format for F, that inverts the value of parameter x
- implicit val fFormat: RootJsonFormat[F] = new RootJsonFormat[F] {
- override def write(f: F): JsValue = JsObject("y" -> f.x.toJson)
- override def read(js: JsValue): F =
- F(js.asJsObject.fields("y").convertTo[Int])
- }
-
- "Overriding with a custom format" should behave like checkRoundtrip(
- F(2),
- """{"y":2}"""
- )
-
- implicit val gFormat: RootJsonFormat[G] = jsonFormat[G]
-
- "Derving a format with a custom child format" should behave like checkRoundtrip(
- G(F(2)),
- """{"f": {"y":2}}"""
- )
-
- case class H(x: Boolean)
- case class I(h: H)
-
- // there is no format defined for H, Magnolia will generate one automatically
- implicit val iFormat: RootJsonFormat[I] = jsonFormat[I]
-
- "Deriving a format that has no implicit child formats available" should behave like checkRoundtrip(
- I(H(true)),
- """{"h": {"x":true}}"""
- )
-
-}