From c5c1aa6bc78b6ebc346befe9f4b434401a683a59 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Fri, 29 Jun 2018 17:56:06 -0700 Subject: Make inclusion of None values as null optional --- CHANGELOG.md | 7 ++- shared/src/main/scala/DerivedFormats.scala | 19 ++++++-- shared/src/test/scala/OptionFieldTests.scala | 53 ++++++++++++++++++++++ shared/src/test/scala/ProductTypeFormatTests.scala | 17 ------- 4 files changed, 74 insertions(+), 22 deletions(-) create mode 100644 shared/src/test/scala/OptionFieldTests.scala diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bdcd86..bf4dbd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Version 0.5.0 + +None values are no longer written to objects by default. This is +configurable by overriding `printNull` in DerivedFormats. + # Version 0.4.7 Upgrade Magnolia from 0.7.1-1 to 0.8.0-1. @@ -5,7 +10,7 @@ # 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: + parameterized types. I.e. it is now possible to derive the following: ``` class A[B](b: B) implicit def fmt[B: JsonFormat] = jsonFormat[A] diff --git a/shared/src/main/scala/DerivedFormats.scala b/shared/src/main/scala/DerivedFormats.scala index 6ea7c4b..ccf48d1 100644 --- a/shared/src/main/scala/DerivedFormats.scala +++ b/shared/src/main/scala/DerivedFormats.scala @@ -13,13 +13,24 @@ trait DerivedFormats { self: BasicFormats => * method can be overriden to use alternative naming conventions. */ @inline def extractFieldName(paramName: String): String = paramName + /** Determines if `None` instances of options should be included in JSON output. + * + * By default, `None` values are ommitted entirely from resulting JSON + * objects. If overridden, they will be included as `null`s instead. + * + * Note that this has no effect in *reading* option types; undefined or null + * values are always converted to `None`. */ + def printNull: Boolean = false + 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 => - extractFieldName(param.label) -> param.typeclass.write( - param.dereference(value) - ) + val fields: Seq[(String, JsValue)] = ctx.parameters.collect { + case param + if !param.option || param.dereference(value) != None || printNull => + extractFieldName(param.label) -> param.typeclass.write( + param.dereference(value) + ) } JsObject(fields: _*) } diff --git a/shared/src/test/scala/OptionFieldTests.scala b/shared/src/test/scala/OptionFieldTests.scala new file mode 100644 index 0000000..8cabf25 --- /dev/null +++ b/shared/src/test/scala/OptionFieldTests.scala @@ -0,0 +1,53 @@ +package spray.json + +import org.scalatest._ + +class OptionFieldTests + extends FlatSpec + with FormatTests { + + case class Opt(x: Option[Int]) + + object HideNull extends DerivedJsonProtocol { + override def printNull = false + implicit val optFmt = jsonFormat[Opt] + } + + object ShowNull extends DerivedJsonProtocol { + override def printNull = true + implicit val optFmt = jsonFormat[Opt] + } + + + "Option fields with some value" should behave like checkRoundtrip( + Opt(Some(2)), + """{"x":2}""" + )(HideNull.optFmt) + + "Option fields with some value (show null)" should behave like checkRoundtrip( + Opt(Some(2)), + """{"x":2}""" + )(ShowNull.optFmt) + + "Option fields with null value" should behave like checkRoundtrip( + Opt(None), + """{}""" + )(HideNull.optFmt) + + "Option fields with null value (show null)" should behave like checkRoundtrip( + Opt(None), + """{"x":null}""" + )(ShowNull.optFmt) + + "Option fields with undefined value" should "deserialize" in { + import HideNull._ + assert("{}".parseJson.convertTo[Opt] == Opt(None)) + } + + "Option fields with undefined value (show null)" should "deserialize" in { + import ShowNull._ + assert("{}".parseJson.convertTo[Opt] == Opt(None)) + } + +} + diff --git a/shared/src/test/scala/ProductTypeFormatTests.scala b/shared/src/test/scala/ProductTypeFormatTests.scala index 4c979c0..a869ac5 100644 --- a/shared/src/test/scala/ProductTypeFormatTests.scala +++ b/shared/src/test/scala/ProductTypeFormatTests.scala @@ -73,23 +73,6 @@ class ProductTypeFormatTests """{"h": {"x":true}}""" ) - case class Opt(x: Option[Int]) - implicit val optFmt = jsonFormat[Opt] - - "Option fields with some value" should behave like checkRoundtrip( - Opt(Some(2)), - """{"x":2}""" - ) - - "Option fields with null value" should behave like checkRoundtrip( - Opt(None), - """{"x":null}""" - ) - - "Option fields with undefined value" should "deserialize" in { - assert("{}".parseJson.convertTo[Opt] == Opt(None)) - } - case class Typed[T](t: T) implicit def typed[T: JsonFormat] = jsonFormat[Typed[T]] -- cgit v1.2.3