From c4dd18f810afdb4adb85d002ad34e660fe54c146 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Fri, 29 Jun 2018 17:56:06 -0700 Subject: Fix bug in derivation of classes with type parameters --- .ci/build | 2 +- CHANGELOG.md | 15 ++++++++++++++ project/plugins.sbt | 2 +- shared/src/main/scala/DerivedFormats.scala | 17 +++++++++------ shared/src/main/scala/annotations.scala | 6 +++--- .../src/test/scala/CoproductTypeFormatTests.scala | 3 ++- shared/src/test/scala/ProductTypeFormatTests.scala | 24 ++++++++++++++++++++++ 7 files changed, 57 insertions(+), 12 deletions(-) diff --git a/.ci/build b/.ci/build index 1c8e832..3354e1a 100755 --- a/.ci/build +++ b/.ci/build @@ -2,7 +2,7 @@ set -ev sbt \ - scalafmtTest \ + scalafmtCheck \ +sprayJsonDerivationJVM/mimaReportBinaryIssues \ +test diff --git a/CHANGELOG.md b/CHANGELOG.md index fa2f894..de43491 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# Version 0.4.6 + +- Fix a bug in the derivation macro that prevented deriving formats for + parameterized types. I.e. it is now possible to write derive the following: + ``` + class A[B](b: B) + implicit def fmt[B: JsonFormat] = jsonFormat[A] + ``` + +- Formatting and documentation tweaks + +# Version 0.4.5 + +Fixes an issue in when serializing field names with an alternate case. + # Version 0.4.4 - Add utility mixins to change serialization of fieldnames, such as diff --git a/project/plugins.sbt b/project/plugins.sbt index d983863..67c3649 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ ivyLoggingLevel := UpdateLogging.Quiet -addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.2.0") +addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.6.0-RC3") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.18") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.4.0") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "0.4.0") diff --git a/shared/src/main/scala/DerivedFormats.scala b/shared/src/main/scala/DerivedFormats.scala index 03e632f..6ea7c4b 100644 --- a/shared/src/main/scala/DerivedFormats.scala +++ b/shared/src/main/scala/DerivedFormats.scala @@ -18,7 +18,8 @@ trait DerivedFormats { self: BasicFormats => override def write(value: T): JsValue = { val fields: Seq[(String, JsValue)] = ctx.parameters.map { param => extractFieldName(param.label) -> param.typeclass.write( - param.dereference(value)) + param.dereference(value) + ) } JsObject(fields: _*) } @@ -40,7 +41,8 @@ trait DerivedFormats { self: BasicFormats => } case js => deserializationError( - s"Cannot read JSON '$js' as a ${ctx.typeName.full}") + s"Cannot read JSON '$js' as a ${ctx.typeName.full}" + ) } } @@ -57,7 +59,8 @@ trait DerivedFormats { self: BasicFormats => case obj: JsObject => JsObject( (Map(typeFieldName -> JsString(sub.typeName.short)) ++ - obj.fields).toSeq: _*) + obj.fields).toSeq: _* + ) case js => js } } @@ -71,7 +74,8 @@ trait DerivedFormats { self: BasicFormats => case _ => deserializationError( s"Cannot deserialize JSON to ${ctx.typeName.full} " + - "because serialized type cannot be determined.") + "because serialized type cannot be determined." + ) } ctx.subtypes.find(_.typeName.short == typeName) match { @@ -79,7 +83,8 @@ trait DerivedFormats { self: BasicFormats => case None => deserializationError( s"Cannot deserialize JSON to ${ctx.typeName.full} " + - s"because type '${typeName}' is unsupported.") + s"because type '${typeName}' is unsupported." + ) } } } @@ -117,7 +122,7 @@ object DerivedFormatMacros { /** 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 tpe = weakTypeOf[T] val sprayPkg = c.mirror.staticPackage("spray.json") val valName = TermName(c.freshName("format")) q"""{ diff --git a/shared/src/main/scala/annotations.scala b/shared/src/main/scala/annotations.scala index 9d35d16..1b7c62c 100644 --- a/shared/src/main/scala/annotations.scala +++ b/shared/src/main/scala/annotations.scala @@ -3,13 +3,13 @@ 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. + * datatype (GADT), and that a 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. + * that would otherwise result in a conflict. * * Example: * {{{ diff --git a/shared/src/test/scala/CoproductTypeFormatTests.scala b/shared/src/test/scala/CoproductTypeFormatTests.scala index de73967..cd6a2ae 100644 --- a/shared/src/test/scala/CoproductTypeFormatTests.scala +++ b/shared/src/test/scala/CoproductTypeFormatTests.scala @@ -54,7 +54,8 @@ class CoproductTypeFormatTests implicit val crazyFormat: RootJsonFormat[Crazy] = jsonFormat[Crazy] "GADT with special characters in type field" should behave like checkRoundtrip[ - Crazy]( + Crazy + ]( CrazyType(), """{"_`crazy type!`\"": "CrazyType"}""" ) diff --git a/shared/src/test/scala/ProductTypeFormatTests.scala b/shared/src/test/scala/ProductTypeFormatTests.scala index 4d71aa3..4c979c0 100644 --- a/shared/src/test/scala/ProductTypeFormatTests.scala +++ b/shared/src/test/scala/ProductTypeFormatTests.scala @@ -90,4 +90,28 @@ class ProductTypeFormatTests assert("{}".parseJson.convertTo[Opt] == Opt(None)) } + case class Typed[T](t: T) + + implicit def typed[T: JsonFormat] = jsonFormat[Typed[T]] + + "Class with a type parameter" should behave like checkRoundtrip( + Typed[Int](42), + """{"t":42}""" + ) + + "Class with nested types" should behave like checkRoundtrip( + Typed[Typed[String]](Typed("hello world")), + """{"t":{"t":"hello world"}}""" + ) + + case class Phantom[T](x: Int) + + // no json format required for T + implicit def phantom[T] = jsonFormat[Phantom[T]] + + "Phantom types without a format" should behave like checkRoundtrip( + Phantom[Int => String](42), // the given type parameter should not have a format + """{"x":42}""" + ) + } -- cgit v1.2.3