aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jakob@odersky.com>2018-03-08 16:13:03 -0800
committerJakob Odersky <jakob@odersky.com>2018-03-08 16:21:49 -0800
commit5ef502abc058358ec3a329c774bb42b9a7bd106f (patch)
treeee5c20fa79307e1a8397a86f31fbe54cbfe9fe1a
parenteb1ad3c956c828d421b7650dd3b01d5129a41a3d (diff)
downloadspray-json-derivation-5ef502abc058358ec3a329c774bb42b9a7bd106f.tar.gz
spray-json-derivation-5ef502abc058358ec3a329c774bb42b9a7bd106f.tar.bz2
spray-json-derivation-5ef502abc058358ec3a329c774bb42b9a7bd106f.zip
Don't ake derived formats implicit by defaultv0.3.0
-rw-r--r--README.md25
-rw-r--r--src/main/scala/DerivedFormats.scala13
-rw-r--r--src/test/scala/CoproductTypeFormatTests.scala (renamed from src/test/scala/CoproductTypeFormats.scala)13
-rw-r--r--src/test/scala/ImplicitDerivedFormatTests.scala26
-rw-r--r--src/test/scala/ProductTypeFormatTests.scala (renamed from src/test/scala/ProductTypeFormats.scala)33
5 files changed, 95 insertions, 15 deletions
diff --git a/README.md b/README.md
index 3c15698..f7c2070 100644
--- a/README.md
+++ b/README.md
@@ -26,14 +26,14 @@ build.sbt:
libraryDependencies += "xyz.driver" %% "spray-json-derivation" % "<latest version>"
```
-Define some case classes and mix `DerivedFormats` into your JSON
+Define some case classes and mix `ImplicitDerivedFormats` into your JSON
protocol stack. That's it.
```scala
import spray.json._
-import xyz.driver.json.DerivedFormats
+import xyz.driver.json.ImplicitDerivedFormats
-object Main extends App with DefaultJsonProtocol with DerivedFormats {
+object Main extends App with DefaultJsonProtocol with ImplicitDerivedFormats {
// Simple case classes
@@ -70,10 +70,27 @@ object Main extends App with DefaultJsonProtocol with DerivedFormats {
}
```
+It is also possible to summon derived formats explicitly by mixing in `DerivedFormats`instead of `ImplicitDerivedFormats`:
+```scala
+import spray.json._
+import xyz.driver.json.DerivedFormats
+
+object Main extends App with DefaultJsonProtocol with DerivedFormats {
+
+ case class A(x: Int)
+ case class B(a: A, str: String)
+
+ implicit val bFormat: RootJsonFormat[B] = jsonFormat[B]
+
+ println(B(A(42), "hello world").toJson.prettyPrint)
+```
+This will have the additional benefit of outputting a stacktrace in case a format cannot be derived, hence making debugging
+much easier.
+
## Documentation
Check out the main file
[DerivedFormats.scala](src/main/scala/DerivedFormats.scala) and the
-[test suite](src/test/scala/ProductTypeFormats.scala) for a complete
+[test suite](src/test/scala/ProductTypeFormatTests.scala) for a complete
overview of the project.
## Development
diff --git a/src/main/scala/DerivedFormats.scala b/src/main/scala/DerivedFormats.scala
index cbfbd61..4339e5c 100644
--- a/src/main/scala/DerivedFormats.scala
+++ b/src/main/scala/DerivedFormats.scala
@@ -5,7 +5,7 @@ import spray.json._
import scala.language.experimental.macros
-/** Mixin that enables automatic derivation of JSON formats for any product
+/** 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]
@@ -75,14 +75,17 @@ trait DerivedFormats { self: BasicFormats =>
}
}
- implicit def derivedFormat[T]: RootJsonFormat[T] =
- macro DerivedFormatHelper.derivedFormat[T]
+ def jsonFormat[T]: RootJsonFormat[T] =
+ macro DerivedFormatMacros.derivedFormat[T]
}
-object DerivedFormats extends DerivedFormats with BasicFormats
+trait ImplicitDerivedFormats extends DerivedFormats { self: BasicFormats =>
+ implicit def implicitJsonFormat[T]: RootJsonFormat[T] =
+ macro DerivedFormatMacros.derivedFormat[T]
+}
-object DerivedFormatHelper {
+object DerivedFormatMacros {
import scala.reflect.macros.whitebox._
/** Utility that converts a magnolia-generated JsonFormat to a RootJsonFormat. */
diff --git a/src/test/scala/CoproductTypeFormats.scala b/src/test/scala/CoproductTypeFormatTests.scala
index 6496e00..ec9b3f5 100644
--- a/src/test/scala/CoproductTypeFormats.scala
+++ b/src/test/scala/CoproductTypeFormatTests.scala
@@ -4,7 +4,7 @@ import spray.json._
import org.scalatest._
-class CoproductTypeFormats
+class CoproductTypeFormatTests
extends FlatSpec
with FormatTests
with DefaultJsonProtocol
@@ -16,6 +16,8 @@ class CoproductTypeFormats
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"}"""
@@ -40,6 +42,8 @@ class CoproductTypeFormats
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"}"""
@@ -49,6 +53,8 @@ class CoproductTypeFormats
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(),
@@ -59,12 +65,15 @@ class CoproductTypeFormats
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 an deserializing as child" should "work" in {
+ "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/ImplicitDerivedFormatTests.scala b/src/test/scala/ImplicitDerivedFormatTests.scala
new file mode 100644
index 0000000..9723cb0
--- /dev/null
+++ b/src/test/scala/ImplicitDerivedFormatTests.scala
@@ -0,0 +1,26 @@
+package xyz.driver.json
+
+import 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/ProductTypeFormats.scala b/src/test/scala/ProductTypeFormatTests.scala
index 02fb554..b7d8d27 100644
--- a/src/test/scala/ProductTypeFormats.scala
+++ b/src/test/scala/ProductTypeFormatTests.scala
@@ -4,7 +4,7 @@ import spray.json._
import org.scalatest._
-class ProductTypeFormats
+class ProductTypeFormatTests
extends FlatSpec
with FormatTests
with DerivedFormats
@@ -16,6 +16,13 @@ class ProductTypeFormats
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(), "{}")
@@ -41,14 +48,32 @@ class ProductTypeFormats
// 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("x" -> JsNumber(-f.x))
+ override def write(f: F): JsValue = JsObject("y" -> f.x.toJson)
override def read(js: JsValue): F =
- F(-js.asJsObject.fields("x").convertTo[Int])
+ F(js.asJsObject.fields("y").convertTo[Int])
}
"Overriding with a custom format" should behave like checkRoundtrip(
F(2),
- """{"x":-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}}"""
)
}