From cdbb36fb1065619c1702d012c6abea91760045e0 Mon Sep 17 00:00:00 2001 From: Thijs Broersen <4889512+ThijsBroersen@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:40:05 +0100 Subject: [PATCH] revert feature excludeEmptyCollections, remove completely (#1236) --- project/BuildHelper.scala | 2 +- .../src/main/scala-2.x/zio/json/macros.scala | 33 +- .../src/main/scala-3/zio/json/macros.scala | 34 +- .../zio/json/JsonCodecConfiguration.scala | 54 +- .../src/main/scala/zio/json/JsonDecoder.scala | 158 ++---- .../src/main/scala/zio/json/JsonEncoder.scala | 33 +- .../scala/zio/json/AnnotationsCodecSpec.scala | 407 -------------- .../json/ConfigurableDeriveCodecSpec.scala | 507 ++---------------- 8 files changed, 122 insertions(+), 1106 deletions(-) diff --git a/project/BuildHelper.scala b/project/BuildHelper.scala index 106fe18c8..5b8643048 100644 --- a/project/BuildHelper.scala +++ b/project/BuildHelper.scala @@ -244,7 +244,7 @@ object BuildHelper { autoAPIMappings := true, unusedCompileDependenciesFilter -= moduleFilter("org.scala-js", "scalajs-library"), mimaPreviousArtifacts := { - previousStableVersion.value.map(organization.value %% name.value % _).toSet ++ + previousStableVersion.value.filter(_ != "0.7.4").map(organization.value %% name.value % _).toSet ++ Set(organization.value %% name.value % "0.7.3") }, mimaCheckDirection := "backward", // TODO: find how we can use "both" for path versions diff --git a/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala b/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala index bc451f495..7ec5366bd 100644 --- a/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala +++ b/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala @@ -19,12 +19,10 @@ final case class jsonField(name: String) extends Annotation */ final case class jsonAliases(alias: String, aliases: String*) extends Annotation -final class jsonExplicitNull extends Annotation - /** - * When disabled keys with empty collections will be omitted from the JSON. + * Empty option fields will be encoded as `null`. */ -final case class jsonExplicitEmptyCollection(enabled: Boolean = true) extends Annotation +final class jsonExplicitNull extends Annotation /** * If used on a sealed class, will determine the name of the field for disambiguating classes. @@ -214,6 +212,7 @@ object DeriveJsonDecoder { if (ctx.parameters.isEmpty) new JsonDecoder[A] { + def unsafeDecode(trace: List[JsonError], in: RetractReader): A = { if (no_extra) { Lexer.char(trace, in, '{') @@ -268,7 +267,7 @@ object DeriveJsonDecoder { ctx.parameters.map(_.typeclass).toArray.asInstanceOf[Array[JsonDecoder[Any]]] private[this] lazy val namesMap = (names.zipWithIndex ++ aliases).toMap - def unsafeDecode(trace: List[JsonError], in: RetractReader): A = { + override def unsafeDecode(trace: List[JsonError], in: RetractReader): A = { Lexer.char(trace, in, '{') // TODO it would be more efficient to have a solution that didn't box @@ -433,6 +432,7 @@ object DeriveJsonEncoder { def join[A](ctx: CaseClass[JsonEncoder, A])(implicit config: JsonCodecConfiguration): JsonEncoder[A] = if (ctx.parameters.isEmpty) new JsonEncoder[A] { + def unsafeEncode(a: A, indent: Option[Int], out: Write): Unit = out.write("{}") override final def toJsonAST(a: A): Either[String, Json] = @@ -457,10 +457,6 @@ object DeriveJsonEncoder { } private[this] val explicitNulls = config.explicitNulls || ctx.annotations.exists(_.isInstanceOf[jsonExplicitNull]) - private[this] val explicitEmptyCollections = - ctx.annotations.collectFirst { case jsonExplicitEmptyCollection(enabled) => - enabled - }.getOrElse(config.explicitEmptyCollections) private[this] lazy val fields = params.map { var idx = 0 p => @@ -468,17 +464,12 @@ object DeriveJsonEncoder { p, names(idx), p.typeclass.asInstanceOf[JsonEncoder[Any]], - explicitNulls || p.annotations.exists(_.isInstanceOf[jsonExplicitNull]), - p.annotations.collectFirst { case jsonExplicitEmptyCollection(enabled) => - enabled - }.getOrElse(explicitEmptyCollections) + explicitNulls || p.annotations.exists(_.isInstanceOf[jsonExplicitNull]) ) idx += 1 field } - override def isEmpty(a: A): Boolean = params.forall(p => p.typeclass.isEmpty(p.dereference(a))) - def unsafeEncode(a: A, indent: Option[Int], out: Write): Unit = { out.write('{') val indent_ = JsonEncoder.bump(indent) @@ -491,8 +482,7 @@ object DeriveJsonEncoder { val p = field._1.dereference(a) if ({ val isNothing = field._3.isNothing(p) - val isEmpty = field._3.isEmpty(p) - (!isNothing && !isEmpty) || (isNothing && field._4) || (isEmpty && field._5) + !isNothing || field._4 }) { // if we have at least one field already, we need a comma if (prevFields) { @@ -518,16 +508,9 @@ object DeriveJsonEncoder { name }.getOrElse(nameTransform(param.label)) val writeNulls = explicitNulls || param.annotations.exists(_.isInstanceOf[jsonExplicitNull]) - val writeEmptyCollections = - param.annotations.collectFirst { case jsonExplicitEmptyCollection(enabled) => - enabled - }.getOrElse(explicitEmptyCollections) c.flatMap { chunk => param.typeclass.toJsonAST(param.dereference(a)).map { value => - if ( - (value == Json.Null && !writeNulls) || - (value.asObject.exists(_.fields.isEmpty) && !writeEmptyCollections) - ) chunk + if (!writeNulls && value == Json.Null) chunk else chunk :+ name -> value } } diff --git a/zio-json/shared/src/main/scala-3/zio/json/macros.scala b/zio-json/shared/src/main/scala-3/zio/json/macros.scala index 4ab9af2c8..e7f8ae583 100644 --- a/zio-json/shared/src/main/scala-3/zio/json/macros.scala +++ b/zio-json/shared/src/main/scala-3/zio/json/macros.scala @@ -32,11 +32,6 @@ final case class jsonAliases(alias: String, aliases: String*) extends Annotation */ final class jsonExplicitNull extends Annotation -/** - * When disabled keys with empty collections will be omitted from the JSON. - */ -final case class jsonExplicitEmptyCollection(enabled: Boolean = true) extends Annotation - /** * If used on a sealed class, will determine the name of the field for * disambiguating classes. @@ -288,7 +283,7 @@ sealed class JsonDecoderDerivation(config: JsonCodecConfiguration) extends Deriv IArray.genericWrapArray(ctx.params.map(_.typeclass)).toArray.asInstanceOf[Array[JsonDecoder[Any]]] private lazy val namesMap = (names.zipWithIndex ++ aliases).toMap - def unsafeDecode(trace: List[JsonError], in: RetractReader): A = { + override def unsafeDecode(trace: List[JsonError], in: RetractReader): A = { Lexer.char(trace, in, '{') val ps = new Array[Any](len) if (Lexer.firstField(trace, in)) @@ -470,6 +465,7 @@ sealed class JsonDecoderDerivation(config: JsonCodecConfiguration) extends Deriv } private lazy val caseObjectEncoder = new JsonEncoder[Any] { + def unsafeEncode(a: Any, indent: Option[Int], out: Write): Unit = out.write("{}") @@ -511,17 +507,15 @@ sealed class JsonEncoderDerivation(config: JsonCodecConfiguration) extends Deriv }.getOrElse(if (transformNames) nameTransform(p.label) else p.label) }.toArray private val explicitNulls = config.explicitNulls || ctx.annotations.exists(_.isInstanceOf[jsonExplicitNull]) - private val explicitEmptyCollections = ctx.annotations.collectFirst { case jsonExplicitEmptyCollection(enabled) => - enabled - }.getOrElse(config.explicitEmptyCollections) private lazy val fields = params.map { var idx = 0 p => - val field = (p, names(idx), p.typeclass.asInstanceOf[JsonEncoder[Any]], - explicitNulls || p.annotations.exists(_.isInstanceOf[jsonExplicitNull]), - p.annotations.collectFirst { case jsonExplicitEmptyCollection(enabled) => - enabled - }.getOrElse(explicitEmptyCollections)) + val field = ( + p, + names(idx), + p.typeclass.asInstanceOf[JsonEncoder[Any]], + explicitNulls || p.annotations.exists(_.isInstanceOf[jsonExplicitNull]) + ) idx += 1 field }.toArray @@ -538,8 +532,7 @@ sealed class JsonEncoderDerivation(config: JsonCodecConfiguration) extends Deriv val p = field._1.deref(a) if ({ val isNothing = field._3.isNothing(p) - val isEmpty = field._3.isEmpty(p) - (!isNothing && !isEmpty) || (isNothing && field._4) || (isEmpty && field._5) + !isNothing || field._4 }) { // if we have at least one field already, we need a comma if (prevFields) { @@ -565,16 +558,9 @@ sealed class JsonEncoderDerivation(config: JsonCodecConfiguration) extends Deriv name }.getOrElse(nameTransform(param.label)) val writeNulls = explicitNulls || param.annotations.exists(_.isInstanceOf[jsonExplicitNull]) - val writeEmptyCollections = - param.annotations.collectFirst { case jsonExplicitEmptyCollection(enabled) => - enabled - }.getOrElse(explicitEmptyCollections) c.flatMap { chunk => param.typeclass.toJsonAST(param.deref(a)).map { value => - if ( - (value == Json.Null && !writeNulls) || - (value.asObject.exists(_.fields.isEmpty) && !writeEmptyCollections) - ) chunk + if (!writeNulls && value == Json.Null) chunk else chunk :+ name -> value } } diff --git a/zio-json/shared/src/main/scala/zio/json/JsonCodecConfiguration.scala b/zio-json/shared/src/main/scala/zio/json/JsonCodecConfiguration.scala index 3c82e8125..9f8428d18 100644 --- a/zio-json/shared/src/main/scala/zio/json/JsonCodecConfiguration.scala +++ b/zio-json/shared/src/main/scala/zio/json/JsonCodecConfiguration.scala @@ -14,77 +14,33 @@ import zio.json.JsonCodecConfiguration.SumTypeHandling.WrapperWithClassNameField * see [[jsonNoExtraFields]] * @param sumTypeMapping * see [[jsonHintNames]] + * @param explicitNulls + * see [[jsonExplicitNull]] */ final case class JsonCodecConfiguration( sumTypeHandling: SumTypeHandling = WrapperWithClassNameField, fieldNameMapping: JsonMemberFormat = IdentityFormat, allowExtraFields: Boolean = true, sumTypeMapping: JsonMemberFormat = IdentityFormat, - explicitNulls: Boolean = false, - explicitEmptyCollections: Boolean = true + explicitNulls: Boolean = false ) { - def this( - sumTypeHandling: SumTypeHandling, - fieldNameMapping: JsonMemberFormat, - allowExtraFields: Boolean, - sumTypeMapping: JsonMemberFormat, - explicitNulls: Boolean - ) = this( - sumTypeHandling, - fieldNameMapping, - allowExtraFields, - sumTypeMapping, - explicitNulls, - true - ) def copy( sumTypeHandling: SumTypeHandling = WrapperWithClassNameField.asInstanceOf[SumTypeHandling], fieldNameMapping: JsonMemberFormat = IdentityFormat.asInstanceOf[JsonMemberFormat], allowExtraFields: Boolean = true, sumTypeMapping: JsonMemberFormat = IdentityFormat.asInstanceOf[JsonMemberFormat], - explicitNulls: Boolean = false, - explicitEmptyCollections: Boolean = true + explicitNulls: Boolean = false ) = new JsonCodecConfiguration( sumTypeHandling, fieldNameMapping, allowExtraFields, sumTypeMapping, - explicitNulls, - explicitEmptyCollections - ) - - def copy( - sumTypeHandling: SumTypeHandling, - fieldNameMapping: JsonMemberFormat, - allowExtraFields: Boolean, - sumTypeMapping: JsonMemberFormat, - explicitNulls: Boolean - ) = new JsonCodecConfiguration( - sumTypeHandling, - fieldNameMapping, - allowExtraFields, - sumTypeMapping, - explicitNulls, - true + explicitNulls ) } object JsonCodecConfiguration { - def apply( - sumTypeHandling: SumTypeHandling, - fieldNameMapping: JsonMemberFormat, - allowExtraFields: Boolean, - sumTypeMapping: JsonMemberFormat, - explicitNulls: Boolean - ) = new JsonCodecConfiguration( - sumTypeHandling, - fieldNameMapping, - allowExtraFields, - sumTypeMapping, - explicitNulls, - true - ) implicit val default: JsonCodecConfiguration = JsonCodecConfiguration() diff --git a/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala b/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala index 4d104eefa..2ca5c4bfb 100644 --- a/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala +++ b/zio-json/shared/src/main/scala/zio/json/JsonDecoder.scala @@ -351,6 +351,7 @@ object JsonDecoder extends GeneratedTupleDecoders with DecoderLowPriority1 with // // If alternative behaviour is desired, e.g. pass null to the underlying, then // use a newtype wrapper. + implicit def option[A](implicit A: JsonDecoder[A]): JsonDecoder[Option[A]] = new JsonDecoder[Option[A]] { self => override def unsafeDecodeMissing(trace: List[JsonError]): Option[A] = None @@ -470,47 +471,36 @@ object JsonDecoder extends GeneratedTupleDecoders with DecoderLowPriority1 with private[json] trait DecoderLowPriority1 extends DecoderLowPriority2 { this: JsonDecoder.type => - implicit def array[A: JsonDecoder: reflect.ClassTag]: JsonDecoder[Array[A]] = - new JsonDecoder[Array[A]] { - - override def unsafeDecodeMissing(trace: List[JsonError]): Array[A] = Array.empty - - def unsafeDecode(trace: List[JsonError], in: RetractReader): Array[A] = - builder(trace, in, Array.newBuilder[A]) - } - - implicit def seq[A: JsonDecoder]: JsonDecoder[Seq[A]] = - new JsonDecoder[Seq[A]] { - - override def unsafeDecodeMissing(trace: List[JsonError]): Seq[A] = - Seq.empty + implicit def array[A: JsonDecoder: reflect.ClassTag]: JsonDecoder[Array[A]] = new JsonDecoder[Array[A]] { - def unsafeDecode(trace: List[JsonError], in: RetractReader): Seq[A] = - builder(trace, in, immutable.Seq.newBuilder[A]) - } + def unsafeDecode(trace: List[JsonError], in: RetractReader): Array[A] = + builder(trace, in, Array.newBuilder[A]) + } - implicit def chunk[A: JsonDecoder]: JsonDecoder[Chunk[A]] = - new JsonDecoder[Chunk[A]] { - private[this] val decoder = JsonDecoder[A] + implicit def seq[A: JsonDecoder]: JsonDecoder[Seq[A]] = new JsonDecoder[Seq[A]] { - override def unsafeDecodeMissing(trace: List[JsonError]): Chunk[A] = Chunk.empty + def unsafeDecode(trace: List[JsonError], in: RetractReader): Seq[A] = + builder(trace, in, immutable.Seq.newBuilder[A]) + } - def unsafeDecode(trace: List[JsonError], in: RetractReader): Chunk[A] = - builder(trace, in, zio.ChunkBuilder.make[A]()) + implicit def chunk[A: JsonDecoder]: JsonDecoder[Chunk[A]] = new JsonDecoder[Chunk[A]] { + private[this] val decoder = JsonDecoder[A] + def unsafeDecode(trace: List[JsonError], in: RetractReader): Chunk[A] = + builder(trace, in, zio.ChunkBuilder.make[A]()) - override final def unsafeFromJsonAST(trace: List[JsonError], json: Json): Chunk[A] = - json match { - case Json.Arr(elements) => - elements.map { - var i = 0 - json => - val span = JsonError.ArrayAccess(i) - i += 1 - decoder.unsafeFromJsonAST(span :: trace, json) - } - case _ => Lexer.error("Not an array", trace) - } - } + override final def unsafeFromJsonAST(trace: List[JsonError], json: Json): Chunk[A] = + json match { + case Json.Arr(elements) => + elements.map { + var i = 0 + json => + val span = JsonError.ArrayAccess(i) + i += 1 + decoder.unsafeFromJsonAST(span :: trace, json) + } + case _ => Lexer.error("Not an array", trace) + } + } implicit def nonEmptyChunk[A: JsonDecoder]: JsonDecoder[NonEmptyChunk[A]] = chunk[A].mapOrFail(NonEmptyChunk.fromChunk(_).toRight("Chunk was empty")) @@ -518,8 +508,6 @@ private[json] trait DecoderLowPriority1 extends DecoderLowPriority2 { implicit def indexedSeq[A: JsonDecoder]: JsonDecoder[IndexedSeq[A]] = new JsonDecoder[IndexedSeq[A]] { - override def unsafeDecodeMissing(trace: List[JsonError]): IndexedSeq[A] = IndexedSeq.empty - def unsafeDecode(trace: List[JsonError], in: RetractReader): IndexedSeq[A] = builder(trace, in, IndexedSeq.newBuilder[A]) } @@ -527,76 +515,50 @@ private[json] trait DecoderLowPriority1 extends DecoderLowPriority2 { implicit def linearSeq[A: JsonDecoder]: JsonDecoder[immutable.LinearSeq[A]] = new JsonDecoder[immutable.LinearSeq[A]] { - override def unsafeDecodeMissing(trace: List[JsonError]): immutable.LinearSeq[A] = - immutable.LinearSeq.empty - def unsafeDecode(trace: List[JsonError], in: RetractReader): LinearSeq[A] = builder(trace, in, immutable.LinearSeq.newBuilder[A]) } - implicit def listSet[A: JsonDecoder]: JsonDecoder[immutable.ListSet[A]] = - new JsonDecoder[immutable.ListSet[A]] { - - override def unsafeDecodeMissing(trace: List[JsonError]): immutable.ListSet[A] = - immutable.ListSet.empty + implicit def listSet[A: JsonDecoder]: JsonDecoder[immutable.ListSet[A]] = new JsonDecoder[immutable.ListSet[A]] { - def unsafeDecode(trace: List[JsonError], in: RetractReader): ListSet[A] = - builder(trace, in, immutable.ListSet.newBuilder[A]) - } + def unsafeDecode(trace: List[JsonError], in: RetractReader): ListSet[A] = + builder(trace, in, immutable.ListSet.newBuilder[A]) + } implicit def treeSet[A: JsonDecoder: Ordering]: JsonDecoder[immutable.TreeSet[A]] = new JsonDecoder[immutable.TreeSet[A]] { - override def unsafeDecodeMissing(trace: List[JsonError]): immutable.TreeSet[A] = - immutable.TreeSet.empty - def unsafeDecode(trace: List[JsonError], in: RetractReader): TreeSet[A] = builder(trace, in, immutable.TreeSet.newBuilder[A]) } - implicit def list[A: JsonDecoder]: JsonDecoder[List[A]] = - new JsonDecoder[List[A]] { + implicit def list[A: JsonDecoder]: JsonDecoder[List[A]] = new JsonDecoder[List[A]] { - override def unsafeDecodeMissing(trace: List[JsonError]): List[A] = List.empty - - def unsafeDecode(trace: List[JsonError], in: RetractReader): List[A] = - builder(trace, in, new mutable.ListBuffer[A]) - } - - implicit def vector[A: JsonDecoder]: JsonDecoder[Vector[A]] = - new JsonDecoder[Vector[A]] { - - override def unsafeDecodeMissing(trace: List[JsonError]): Vector[A] = - Vector.empty - - def unsafeDecode(trace: List[JsonError], in: RetractReader): Vector[A] = - builder(trace, in, immutable.Vector.newBuilder[A]) - } + def unsafeDecode(trace: List[JsonError], in: RetractReader): List[A] = + builder(trace, in, new mutable.ListBuffer[A]) + } - implicit def set[A: JsonDecoder]: JsonDecoder[Set[A]] = - new JsonDecoder[Set[A]] { + implicit def vector[A: JsonDecoder]: JsonDecoder[Vector[A]] = new JsonDecoder[Vector[A]] { - override def unsafeDecodeMissing(trace: List[JsonError]): Set[A] = Set.empty + def unsafeDecode(trace: List[JsonError], in: RetractReader): Vector[A] = + builder(trace, in, immutable.Vector.newBuilder[A]) + } - def unsafeDecode(trace: List[JsonError], in: RetractReader): Set[A] = - builder(trace, in, Set.newBuilder[A]) - } + implicit def set[A: JsonDecoder]: JsonDecoder[Set[A]] = new JsonDecoder[Set[A]] { - implicit def hashSet[A: JsonDecoder]: JsonDecoder[immutable.HashSet[A]] = - new JsonDecoder[immutable.HashSet[A]] { + def unsafeDecode(trace: List[JsonError], in: RetractReader): Set[A] = + builder(trace, in, Set.newBuilder[A]) + } - override def unsafeDecodeMissing(trace: List[JsonError]): immutable.HashSet[A] = - immutable.HashSet.empty + implicit def hashSet[A: JsonDecoder]: JsonDecoder[immutable.HashSet[A]] = new JsonDecoder[immutable.HashSet[A]] { - def unsafeDecode(trace: List[JsonError], in: RetractReader): immutable.HashSet[A] = - builder(trace, in, immutable.HashSet.newBuilder[A]) - } + def unsafeDecode(trace: List[JsonError], in: RetractReader): immutable.HashSet[A] = + builder(trace, in, immutable.HashSet.newBuilder[A]) + } implicit def map[K: JsonFieldDecoder, V: JsonDecoder]: JsonDecoder[Map[K, V]] = new JsonDecoder[Map[K, V]] { - override def unsafeDecodeMissing(trace: List[JsonError]): Map[K, V] = Map.empty - def unsafeDecode(trace: List[JsonError], in: RetractReader): Map[K, V] = keyValueBuilder(trace, in, Map.newBuilder[K, V]) } @@ -604,9 +566,6 @@ private[json] trait DecoderLowPriority1 extends DecoderLowPriority2 { implicit def hashMap[K: JsonFieldDecoder, V: JsonDecoder]: JsonDecoder[immutable.HashMap[K, V]] = new JsonDecoder[immutable.HashMap[K, V]] { - override def unsafeDecodeMissing(trace: List[JsonError]): immutable.HashMap[K, V] = - immutable.HashMap.empty - def unsafeDecode(trace: List[JsonError], in: RetractReader): immutable.HashMap[K, V] = keyValueBuilder(trace, in, immutable.HashMap.newBuilder[K, V]) } @@ -614,8 +573,6 @@ private[json] trait DecoderLowPriority1 extends DecoderLowPriority2 { implicit def mutableMap[K: JsonFieldDecoder, V: JsonDecoder]: JsonDecoder[mutable.Map[K, V]] = new JsonDecoder[mutable.Map[K, V]] { - override def unsafeDecodeMissing(trace: List[JsonError]): mutable.Map[K, V] = mutable.Map.empty - def unsafeDecode(trace: List[JsonError], in: RetractReader): mutable.Map[K, V] = keyValueBuilder(trace, in, mutable.Map.newBuilder[K, V]) } @@ -623,9 +580,6 @@ private[json] trait DecoderLowPriority1 extends DecoderLowPriority2 { implicit def sortedSet[A: Ordering: JsonDecoder]: JsonDecoder[immutable.SortedSet[A]] = new JsonDecoder[immutable.SortedSet[A]] { - override def unsafeDecodeMissing(trace: List[JsonError]): immutable.SortedSet[A] = - immutable.SortedSet.empty - def unsafeDecode(trace: List[JsonError], in: RetractReader): immutable.SortedSet[A] = builder(trace, in, immutable.SortedSet.newBuilder[A]) } @@ -633,9 +587,6 @@ private[json] trait DecoderLowPriority1 extends DecoderLowPriority2 { implicit def sortedMap[K: JsonFieldDecoder: Ordering, V: JsonDecoder]: JsonDecoder[collection.SortedMap[K, V]] = new JsonDecoder[collection.SortedMap[K, V]] { - override def unsafeDecodeMissing(trace: List[JsonError]): collection.SortedMap[K, V] = - collection.SortedMap.empty - def unsafeDecode(trace: List[JsonError], in: RetractReader): collection.SortedMap[K, V] = keyValueBuilder(trace, in, collection.SortedMap.newBuilder[K, V]) } @@ -643,9 +594,6 @@ private[json] trait DecoderLowPriority1 extends DecoderLowPriority2 { implicit def listMap[K: JsonFieldDecoder, V: JsonDecoder]: JsonDecoder[immutable.ListMap[K, V]] = new JsonDecoder[immutable.ListMap[K, V]] { - override def unsafeDecodeMissing(trace: List[JsonError]): immutable.ListMap[K, V] = - immutable.ListMap.empty - def unsafeDecode(trace: List[JsonError], in: RetractReader): immutable.ListMap[K, V] = keyValueBuilder(trace, in, immutable.ListMap.newBuilder[K, V]) } @@ -665,14 +613,11 @@ private[json] trait DecoderLowPriority1 extends DecoderLowPriority2 { private[json] trait DecoderLowPriority2 extends DecoderLowPriority3 { this: JsonDecoder.type => - implicit def iterable[A: JsonDecoder]: JsonDecoder[Iterable[A]] = - new JsonDecoder[Iterable[A]] { - - override def unsafeDecodeMissing(trace: List[JsonError]): Iterable[A] = Iterable.empty + implicit def iterable[A: JsonDecoder]: JsonDecoder[Iterable[A]] = new JsonDecoder[Iterable[A]] { - def unsafeDecode(trace: List[JsonError], in: RetractReader): Iterable[A] = - builder(trace, in, immutable.Iterable.newBuilder[A]) - } + def unsafeDecode(trace: List[JsonError], in: RetractReader): Iterable[A] = + builder(trace, in, immutable.Iterable.newBuilder[A]) + } // not implicit because this overlaps with decoders for lists of tuples def keyValueChunk[K, A](implicit @@ -681,9 +626,6 @@ private[json] trait DecoderLowPriority2 extends DecoderLowPriority3 { ): JsonDecoder[Chunk[(K, A)]] = new JsonDecoder[Chunk[(K, A)]] { - override def unsafeDecodeMissing(trace: List[JsonError]): Chunk[(K, A)] = - Chunk.empty - def unsafeDecode(trace: List[JsonError], in: RetractReader): Chunk[(K, A)] = keyValueBuilder[K, A, ({ type lambda[X, Y] = Chunk[(X, Y)] })#lambda]( trace, diff --git a/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala b/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala index ca09d2977..7bed24320 100644 --- a/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala +++ b/zio-json/shared/src/main/scala/zio/json/JsonEncoder.scala @@ -39,8 +39,6 @@ trait JsonEncoder[A] extends JsonEncoderPlatformSpecific[A] { override def isNothing(b: B): Boolean = self.isNothing(f(b)) - override def isEmpty(b: B): Boolean = self.isEmpty(f(b)) - override final def toJsonAST(b: B): Either[String, Json] = self.toJsonAST(f(b)) } @@ -80,11 +78,6 @@ trait JsonEncoder[A] extends JsonEncoderPlatformSpecific[A] { */ def isNothing(a: A): Boolean = false - /** - * This default may be overridden when this value may be empty within a JSON object and still be encoded. - */ - def isEmpty(a: A): Boolean = false - /** * Returns this encoder but narrowed to the its given sub-type */ @@ -209,8 +202,6 @@ object JsonEncoder extends GeneratedTupleEncoders with EncoderLowPriority1 with override def isNothing(a: A): Boolean = encoder.isNothing(a) - override def isEmpty(a: A): Boolean = encoder.isEmpty(a) - override def toJsonAST(a: A): Either[String, Json] = encoder.toJsonAST(a) } @@ -320,14 +311,8 @@ object JsonEncoder extends GeneratedTupleEncoders with EncoderLowPriority1 with private[json] trait EncoderLowPriority1 extends EncoderLowPriority2 { this: JsonEncoder.type => - implicit def array[A](implicit - A: JsonEncoder[A], - classTag: ClassTag[A] - ): JsonEncoder[Array[A]] = + implicit def array[A](implicit A: JsonEncoder[A], classTag: ClassTag[A]): JsonEncoder[Array[A]] = new JsonEncoder[Array[A]] { - - override def isEmpty(as: Array[A]): Boolean = as.isEmpty - def unsafeEncode(as: Array[A], indent: Option[Int], out: Write): Unit = if (as.isEmpty) out.write("[]") else { @@ -389,11 +374,9 @@ private[json] trait EncoderLowPriority1 extends EncoderLowPriority2 { implicit def vector[A: JsonEncoder]: JsonEncoder[Vector[A]] = iterable[A, Vector] - implicit def set[A: JsonEncoder]: JsonEncoder[Set[A]] = - iterable[A, Set] + implicit def set[A: JsonEncoder]: JsonEncoder[Set[A]] = iterable[A, Set] - implicit def hashSet[A: JsonEncoder]: JsonEncoder[immutable.HashSet[A]] = - iterable[A, immutable.HashSet] + implicit def hashSet[A: JsonEncoder]: JsonEncoder[immutable.HashSet[A]] = iterable[A, immutable.HashSet] implicit def sortedSet[A: Ordering: JsonEncoder]: JsonEncoder[immutable.SortedSet[A]] = iterable[A, immutable.SortedSet] @@ -417,13 +400,8 @@ private[json] trait EncoderLowPriority1 extends EncoderLowPriority2 { private[json] trait EncoderLowPriority2 extends EncoderLowPriority3 { this: JsonEncoder.type => - implicit def iterable[A, T[X] <: Iterable[X]](implicit - A: JsonEncoder[A] - ): JsonEncoder[T[A]] = + implicit def iterable[A, T[X] <: Iterable[X]](implicit A: JsonEncoder[A]): JsonEncoder[T[A]] = new JsonEncoder[T[A]] { - - override def isEmpty(as: T[A]): Boolean = as.isEmpty - def unsafeEncode(as: T[A], indent: Option[Int], out: Write): Unit = if (as.isEmpty) out.write("[]") else { @@ -471,9 +449,6 @@ private[json] trait EncoderLowPriority2 extends EncoderLowPriority3 { K: JsonFieldEncoder[K], A: JsonEncoder[A] ): JsonEncoder[T[K, A]] = new JsonEncoder[T[K, A]] { - - override def isEmpty(a: T[K, A]): Boolean = a.isEmpty - def unsafeEncode(kvs: T[K, A], indent: Option[Int], out: Write): Unit = if (kvs.isEmpty) out.write("{}") else { diff --git a/zio-json/shared/src/test/scala/zio/json/AnnotationsCodecSpec.scala b/zio-json/shared/src/test/scala/zio/json/AnnotationsCodecSpec.scala index 429c93c1c..e6769d013 100644 --- a/zio-json/shared/src/test/scala/zio/json/AnnotationsCodecSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/AnnotationsCodecSpec.scala @@ -2,10 +2,6 @@ package zio.json import zio.json.ast.Json import zio.test._ -import zio.Chunk - -import scala.collection.immutable -import scala.collection.mutable object AnnotationsCodecSpec extends ZIOSpecDefault { @@ -90,17 +86,6 @@ object AnnotationsCodecSpec extends ZIOSpecDefault { expectedStr.fromJson[OptionalField].toOption.get == expectedObj, expectedObj.toJson == expectedStr ) - }, - test("do not write empty collections") { - @jsonExplicitEmptyCollection(false) - case class EmptySeq(a: Seq[Int]) - - val expectedStr = """{}""" - val expectedObj = EmptySeq(Seq.empty) - - implicit val codec: JsonCodec[EmptySeq] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptySeq].toOption.get == expectedObj, expectedObj.toJson == expectedStr) } ), suite("AST")( @@ -159,398 +144,6 @@ object AnnotationsCodecSpec extends ZIOSpecDefault { implicit val codec: JsonCodec[OptionalField] = DeriveJsonCodec.gen assertTrue(jsonAST.as[OptionalField].toOption.get == expectedObj, expectedObj.toJsonAST == Right(jsonAST)) - }, - test("do not write empty collections") { - @jsonExplicitEmptyCollection(false) - case class EmptySeq(a: Seq[Int]) - - val jsonAST = Json.Obj("a" -> Json.Arr()) - val expectedObj = EmptySeq(Seq.empty) - - implicit val codec: JsonCodec[EmptySeq] = DeriveJsonCodec.gen - - assertTrue(jsonAST.as[EmptySeq].toOption.get == expectedObj, expectedObj.toJsonAST == Right(jsonAST)) - } - ) - ), - suite("explicit empty collections")( - suite("should write empty collections if set to true")( - test("for an array") { - @jsonExplicitEmptyCollection(true) - case class EmptyArray(a: Array[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyArray(Array.empty) - - implicit val codec: JsonCodec[EmptyArray] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyArray].toOption.get.a.isEmpty, expectedObj.toJson == expectedStr) - }, - test("for a seq") { - @jsonExplicitEmptyCollection(true) - case class EmptySeq(a: Seq[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptySeq(Seq.empty) - - implicit val codec: JsonCodec[EmptySeq] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptySeq].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a chunk") { - @jsonExplicitEmptyCollection(true) - case class EmptyChunk(a: Chunk[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyChunk(Chunk.empty) - - implicit val codec: JsonCodec[EmptyChunk] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyChunk].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for an indexed seq") { - case class EmptyIndexedSeq(a: IndexedSeq[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyIndexedSeq(IndexedSeq.empty) - - implicit val codec: JsonCodec[EmptyIndexedSeq] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyIndexedSeq].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a linear seq") { - @jsonExplicitEmptyCollection(true) - case class EmptyLinearSeq(a: immutable.LinearSeq[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyLinearSeq(immutable.LinearSeq.empty) - - implicit val codec: JsonCodec[EmptyLinearSeq] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyLinearSeq].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a list set") { - @jsonExplicitEmptyCollection(true) - case class EmptyListSet(a: immutable.ListSet[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyListSet(immutable.ListSet.empty) - - implicit val codec: JsonCodec[EmptyListSet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyListSet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a tree set") { - @jsonExplicitEmptyCollection(true) - case class EmptyTreeSet(a: immutable.TreeSet[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyTreeSet(immutable.TreeSet.empty) - - implicit val codec: JsonCodec[EmptyTreeSet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyTreeSet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a list") { - @jsonExplicitEmptyCollection(true) - case class EmptyList(a: List[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyList(List.empty) - - implicit val codec: JsonCodec[EmptyList] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyList].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a vector") { - @jsonExplicitEmptyCollection(true) - case class EmptyVector(a: Vector[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyVector(Vector.empty) - - implicit val codec: JsonCodec[EmptyVector] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyVector].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a set") { - @jsonExplicitEmptyCollection(true) - case class EmptySet(a: Set[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptySet(Set.empty) - - implicit val codec: JsonCodec[EmptySet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptySet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a hash set") { - @jsonExplicitEmptyCollection(true) - case class EmptyHashSet(a: immutable.HashSet[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyHashSet(immutable.HashSet.empty) - - implicit val codec: JsonCodec[EmptyHashSet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyHashSet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a sorted set") { - @jsonExplicitEmptyCollection(true) - case class EmptySortedSet(a: immutable.SortedSet[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptySortedSet(immutable.SortedSet.empty) - - implicit val codec: JsonCodec[EmptySortedSet] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptySortedSet].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a map") { - @jsonExplicitEmptyCollection(true) - case class EmptyMap(a: Map[String, String]) - val expectedStr = """{"a":{}}""" - val expectedObj = EmptyMap(Map.empty) - - implicit val codec: JsonCodec[EmptyMap] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyMap].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a hash map") { - @jsonExplicitEmptyCollection(true) - case class EmptyHashMap(a: immutable.HashMap[String, String]) - val expectedStr = """{"a":{}}""" - val expectedObj = EmptyHashMap(immutable.HashMap.empty) - - implicit val codec: JsonCodec[EmptyHashMap] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyHashMap].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a mutable map") { - @jsonExplicitEmptyCollection(true) - case class EmptyMutableMap(a: mutable.Map[String, String]) - val expectedStr = """{"a":{}}""" - val expectedObj = EmptyMutableMap(mutable.Map.empty) - - implicit val codec: JsonCodec[EmptyMutableMap] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyMutableMap].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a sorted map") { - @jsonExplicitEmptyCollection(true) - case class EmptySortedMap(a: collection.SortedMap[String, String]) - val expectedStr = """{"a":{}}""" - val expectedObj = EmptySortedMap(collection.SortedMap.empty) - - implicit val codec: JsonCodec[EmptySortedMap] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptySortedMap].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a list map") { - @jsonExplicitEmptyCollection(true) - case class EmptyListMap(a: immutable.ListMap[String, String]) - val expectedStr = """{"a":{}}""" - val expectedObj = EmptyListMap(immutable.ListMap.empty) - - implicit val codec: JsonCodec[EmptyListMap] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyListMap].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - } - ), - suite("should not write empty collections if set to false")( - test("for an array") { - @jsonExplicitEmptyCollection(false) - case class EmptyArray(a: Array[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyArray(Array.empty) - - implicit val codec: JsonCodec[EmptyArray] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyArray].toOption.get.a.isEmpty, expectedObj.toJson == expectedStr) - }, - test("for a seq") { - @jsonExplicitEmptyCollection(false) - case class EmptySeq(a: Seq[Int]) - val expectedStr = """{}""" - val expectedObj = EmptySeq(Seq.empty) - - implicit val codec: JsonCodec[EmptySeq] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptySeq].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a chunk") { - @jsonExplicitEmptyCollection(false) - case class EmptyChunk(a: Chunk[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyChunk(Chunk.empty) - - implicit val codec: JsonCodec[EmptyChunk] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyChunk].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for an indexed seq") { - @jsonExplicitEmptyCollection(false) - case class EmptyIndexedSeq(a: IndexedSeq[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyIndexedSeq(IndexedSeq.empty) - - implicit val codec: JsonCodec[EmptyIndexedSeq] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyIndexedSeq].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a linear seq") { - @jsonExplicitEmptyCollection(false) - case class EmptyLinearSeq(a: immutable.LinearSeq[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyLinearSeq(immutable.LinearSeq.empty) - - implicit val codec: JsonCodec[EmptyLinearSeq] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyLinearSeq].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a list set") { - @jsonExplicitEmptyCollection(false) - case class EmptyListSet(a: immutable.ListSet[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyListSet(immutable.ListSet.empty) - - implicit val codec: JsonCodec[EmptyListSet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyListSet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a treeSet") { - @jsonExplicitEmptyCollection(false) - case class EmptyTreeSet(a: immutable.TreeSet[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyTreeSet(immutable.TreeSet.empty) - - implicit val codec: JsonCodec[EmptyTreeSet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyTreeSet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a list") { - @jsonExplicitEmptyCollection(false) - case class EmptyList(a: List[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyList(List.empty) - - implicit val codec: JsonCodec[EmptyList] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyList].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a vector") { - @jsonExplicitEmptyCollection(false) - case class EmptyVector(a: Vector[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyVector(Vector.empty) - - implicit val codec: JsonCodec[EmptyVector] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyVector].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a set") { - @jsonExplicitEmptyCollection(false) - case class EmptySet(a: Set[Int]) - val expectedStr = """{}""" - val expectedObj = EmptySet(Set.empty) - - implicit val codec: JsonCodec[EmptySet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptySet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a hash set") { - @jsonExplicitEmptyCollection(false) - case class EmptyHashSet(a: immutable.HashSet[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyHashSet(immutable.HashSet.empty) - - implicit val codec: JsonCodec[EmptyHashSet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyHashSet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a sorted set") { - @jsonExplicitEmptyCollection(false) - case class EmptySortedSet(a: immutable.SortedSet[Int]) - val expectedStr = """{}""" - val expectedObj = EmptySortedSet(immutable.SortedSet.empty) - - implicit val codec: JsonCodec[EmptySortedSet] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptySortedSet].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a map") { - @jsonExplicitEmptyCollection(false) - case class EmptyMap(a: Map[String, String]) - val expectedStr = """{}""" - val expectedObj = EmptyMap(Map.empty) - - implicit val codec: JsonCodec[EmptyMap] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyMap].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a hashMap") { - @jsonExplicitEmptyCollection(false) - case class EmptyHashMap(a: immutable.HashMap[String, String]) - val expectedStr = """{}""" - val expectedObj = EmptyHashMap(immutable.HashMap.empty) - - implicit val codec: JsonCodec[EmptyHashMap] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyHashMap].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a mutable map") { - @jsonExplicitEmptyCollection(false) - case class EmptyMutableMap(a: mutable.Map[String, String]) - val expectedStr = """{}""" - val expectedObj = EmptyMutableMap(mutable.Map.empty) - - implicit val codec: JsonCodec[EmptyMutableMap] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyMutableMap].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a sorted map") { - @jsonExplicitEmptyCollection(false) - case class EmptySortedMap(a: collection.SortedMap[String, String]) - val expectedStr = """{}""" - val expectedObj = EmptySortedMap(collection.SortedMap.empty) - - implicit val codec: JsonCodec[EmptySortedMap] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptySortedMap].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a list map") { - @jsonExplicitEmptyCollection(false) - case class EmptyListMap(a: immutable.ListMap[String, String]) - val expectedStr = """{}""" - val expectedObj = EmptyListMap(immutable.ListMap.empty) - - implicit val codec: JsonCodec[EmptyListMap] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyListMap].toOption.get == expectedObj, expectedObj.toJson == expectedStr) } ) ) diff --git a/zio-json/shared/src/test/scala/zio/json/ConfigurableDeriveCodecSpec.scala b/zio-json/shared/src/test/scala/zio/json/ConfigurableDeriveCodecSpec.scala index 75671f80a..8fc2df6cf 100644 --- a/zio-json/shared/src/test/scala/zio/json/ConfigurableDeriveCodecSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/ConfigurableDeriveCodecSpec.scala @@ -3,10 +3,6 @@ package zio.json import zio.json.JsonCodecConfiguration.SumTypeHandling.DiscriminatorField import zio.json.ast.Json import zio.test._ -import zio.Chunk - -import scala.collection.immutable -import scala.collection.mutable object ConfigurableDeriveCodecSpec extends ZIOSpecDefault { case class ClassWithFields(someField: Int, someOtherField: String) @@ -54,29 +50,43 @@ object ConfigurableDeriveCodecSpec extends ZIOSpecDefault { assertTrue( jsonStr.fromJson[ClassWithFields].toOption.get == expectedObj ) - } - ), - test("do not write nulls by default") { - val expectedStr = """{}""" - val expectedObj = OptionalField(None) + }, + test("do not write nulls by default, decode missing nulls as None") { + val expectedStr = """{}""" + val expectedObj = OptionalField(None) - implicit val codec: JsonCodec[OptionalField] = DeriveJsonCodec.gen + implicit val codec: JsonCodec[OptionalField] = DeriveJsonCodec.gen - assertTrue( - expectedStr.fromJson[OptionalField].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("write empty collections by default") { - case class EmptySeq(a: Seq[Int]) + assertTrue( + expectedStr.fromJson[OptionalField].toOption.get == expectedObj, + expectedObj.toJson == expectedStr + ) + }, + test("write empty collections by default") { + case class EmptySeq(a: Seq[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptySeq(Seq.empty) + val expectedStr = """{"a":[]}""" + val expectedObj = EmptySeq(Seq.empty) - implicit val codec: JsonCodec[EmptySeq] = DeriveJsonCodec.gen + implicit val codec: JsonCodec[EmptySeq] = DeriveJsonCodec.gen - assertTrue(expectedStr.fromJson[EmptySeq].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, + assertTrue(expectedStr.fromJson[EmptySeq].toOption.get == expectedObj, expectedObj.toJson == expectedStr) + }, + test("fail on decoding missing empty collections by default") { + case class Empty(z: Option[Int]) + case class EmptyObj(a: Empty) + case class EmptySeq(a: Seq[Int]) + + implicit val codecEmpty: JsonCodec[Empty] = DeriveJsonCodec.gen[Empty] + implicit val codecEmptyObj: JsonCodec[EmptyObj] = DeriveJsonCodec.gen[EmptyObj] + implicit val codecEmptySeq: JsonCodec[EmptySeq] = DeriveJsonCodec.gen[EmptySeq] + + assertTrue( + """{}""".fromJson[EmptyObj] == Left(".a(missing)"), + """{}""".fromJson[EmptySeq] == Left(".a(missing)") + ) + } + ), suite("AST")( test("should not map field names by default") { val expectedAST = Json.Obj("someField" -> Json.Num(1), "someOtherField" -> Json.Str("a")) @@ -122,12 +132,14 @@ object ConfigurableDeriveCodecSpec extends ZIOSpecDefault { ) }, test("write empty collections by default") { - case class EmptySeq(a: Seq[Int]) + case class Empty() + case class EmptySeq(a: Seq[Int], b: Empty) - val jsonAST = Json.Obj("a" -> Json.Arr()) - val expectedObj = EmptySeq(Seq.empty) + val jsonAST = Json.Obj("a" -> Json.Arr(), "b" -> Json.Obj()) + val expectedObj = EmptySeq(Seq.empty, Empty()) - implicit val codec: JsonCodec[EmptySeq] = DeriveJsonCodec.gen + implicit val emptyCodec: JsonCodec[Empty] = DeriveJsonCodec.gen + implicit val codec: JsonCodec[EmptySeq] = DeriveJsonCodec.gen assertTrue( jsonAST.as[EmptySeq].toOption.get == expectedObj, @@ -200,18 +212,6 @@ object ConfigurableDeriveCodecSpec extends ZIOSpecDefault { expectedStr.fromJson[OptionalField].toOption.get == expectedObj, expectedObj.toJson == expectedStr ) - }, - test("do not write empty collections") { - case class EmptySeq(a: Seq[Int]) - - val expectedStr = """{}""" - val expectedObj = EmptySeq(Seq.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptySeq] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptySeq].toOption.get == expectedObj, expectedObj.toJson == expectedStr) } ), suite("AST")( @@ -262,434 +262,15 @@ object ConfigurableDeriveCodecSpec extends ZIOSpecDefault { assertTrue(jsonAST.as[OptionalField].toOption.get == expectedObj, expectedObj.toJsonAST == Right(jsonAST)) }, - test("do not write empty collections") { - case class EmptySeq(a: Seq[Int]) - - val jsonAST = Json.Obj("a" -> Json.Arr()) - val expectedObj = EmptySeq(Seq.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptySeq] = DeriveJsonCodec.gen - - assertTrue(jsonAST.as[EmptySeq].toOption.get == expectedObj, expectedObj.toJsonAST == Right(jsonAST)) - } - ) - ), - suite("explicit empty collections")( - suite("should write empty collections if set to true")( - test("for an array") { - case class EmptyArray(a: Array[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyArray(Array.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyArray] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyArray].toOption.get.a.isEmpty, expectedObj.toJson == expectedStr) - }, - test("for a seq") { - case class EmptySeq(a: Seq[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptySeq(Seq.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptySeq] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptySeq].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a chunk") { - case class EmptyChunk(a: Chunk[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyChunk(Chunk.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyChunk] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyChunk].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for an indexed seq") { - case class EmptyIndexedSeq(a: IndexedSeq[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyIndexedSeq(IndexedSeq.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyIndexedSeq] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyIndexedSeq].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a linear seq") { - case class EmptyLinearSeq(a: immutable.LinearSeq[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyLinearSeq(immutable.LinearSeq.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyLinearSeq] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyLinearSeq].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a list set") { - case class EmptyListSet(a: immutable.ListSet[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyListSet(immutable.ListSet.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyListSet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyListSet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a tree set") { - case class EmptyTreeSet(a: immutable.TreeSet[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyTreeSet(immutable.TreeSet.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyTreeSet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyTreeSet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a list") { - case class EmptyList(a: List[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyList(List.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyList] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyList].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a vector") { - case class EmptyVector(a: Vector[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyVector(Vector.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyVector] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyVector].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a set") { - case class EmptySet(a: Set[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptySet(Set.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptySet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptySet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a hash set") { - case class EmptyHashSet(a: immutable.HashSet[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptyHashSet(immutable.HashSet.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyHashSet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyHashSet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a sorted set") { - case class EmptySortedSet(a: immutable.SortedSet[Int]) - val expectedStr = """{"a":[]}""" - val expectedObj = EmptySortedSet(immutable.SortedSet.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptySortedSet] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptySortedSet].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a map") { - case class EmptyMap(a: Map[String, String]) - val expectedStr = """{"a":{}}""" - val expectedObj = EmptyMap(Map.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyMap] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyMap].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a hash map") { - case class EmptyHashMap(a: immutable.HashMap[String, String]) - val expectedStr = """{"a":{}}""" - val expectedObj = EmptyHashMap(immutable.HashMap.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyHashMap] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyHashMap].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a mutable map") { - case class EmptyMutableMap(a: mutable.Map[String, String]) - val expectedStr = """{"a":{}}""" - val expectedObj = EmptyMutableMap(mutable.Map.empty) + test("fail on decoding missing explicit nulls") { + val jsonStr = """{}""" implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyMutableMap] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyMutableMap].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a sorted map") { - case class EmptySortedMap(a: collection.SortedMap[String, String]) - val expectedStr = """{"a":{}}""" - val expectedObj = EmptySortedMap(collection.SortedMap.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptySortedMap] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptySortedMap].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a list map") { - case class EmptyListMap(a: immutable.ListMap[String, String]) - val expectedStr = """{"a":{}}""" - val expectedObj = EmptyListMap(immutable.ListMap.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = true) - implicit val codec: JsonCodec[EmptyListMap] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyListMap].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - } - ), - suite("should not write empty collections if set to false")( - test("for an array") { - case class EmptyArray(a: Array[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyArray(Array.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyArray] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyArray].toOption.get.a.isEmpty, expectedObj.toJson == expectedStr) - }, - test("for a seq") { - case class EmptySeq(a: Seq[Int]) - val expectedStr = """{}""" - val expectedObj = EmptySeq(Seq.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptySeq] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptySeq].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a chunk") { - case class EmptyChunk(a: Chunk[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyChunk(Chunk.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyChunk] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyChunk].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for an indexed seq") { - case class EmptyIndexedSeq(a: IndexedSeq[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyIndexedSeq(IndexedSeq.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyIndexedSeq] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyIndexedSeq].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a linear seq") { - case class EmptyLinearSeq(a: immutable.LinearSeq[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyLinearSeq(immutable.LinearSeq.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyLinearSeq] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyLinearSeq].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a list set") { - case class EmptyListSet(a: immutable.ListSet[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyListSet(immutable.ListSet.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyListSet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyListSet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a treeSet") { - case class EmptyTreeSet(a: immutable.TreeSet[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyTreeSet(immutable.TreeSet.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyTreeSet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyTreeSet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a list") { - case class EmptyList(a: List[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyList(List.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyList] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyList].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a vector") { - case class EmptyVector(a: Vector[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyVector(Vector.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyVector] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyVector].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a set") { - case class EmptySet(a: Set[Int]) - val expectedStr = """{}""" - val expectedObj = EmptySet(Set.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptySet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptySet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a hash set") { - case class EmptyHashSet(a: immutable.HashSet[Int]) - val expectedStr = """{}""" - val expectedObj = EmptyHashSet(immutable.HashSet.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyHashSet] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyHashSet].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a sorted set") { - case class EmptySortedSet(a: immutable.SortedSet[Int]) - val expectedStr = """{}""" - val expectedObj = EmptySortedSet(immutable.SortedSet.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptySortedSet] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptySortedSet].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a map") { - case class EmptyMap(a: Map[String, String]) - val expectedStr = """{}""" - val expectedObj = EmptyMap(Map.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyMap] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyMap].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a hashMap") { - case class EmptyHashMap(a: immutable.HashMap[String, String]) - val expectedStr = """{}""" - val expectedObj = EmptyHashMap(immutable.HashMap.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyHashMap] = DeriveJsonCodec.gen - - assertTrue(expectedStr.fromJson[EmptyHashMap].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - }, - test("for a mutable map") { - case class EmptyMutableMap(a: mutable.Map[String, String]) - val expectedStr = """{}""" - val expectedObj = EmptyMutableMap(mutable.Map.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyMutableMap] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptyMutableMap].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a sorted map") { - case class EmptySortedMap(a: collection.SortedMap[String, String]) - val expectedStr = """{}""" - val expectedObj = EmptySortedMap(collection.SortedMap.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptySortedMap] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[EmptySortedMap].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("for a list map") { - case class EmptyListMap(a: immutable.ListMap[String, String]) - val expectedStr = """{}""" - val expectedObj = EmptyListMap(immutable.ListMap.empty) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(explicitEmptyCollections = false) - implicit val codec: JsonCodec[EmptyListMap] = DeriveJsonCodec.gen + JsonCodecConfiguration(explicitNulls = true) + implicit val codec: JsonCodec[OptionalField] = DeriveJsonCodec.gen - assertTrue(expectedStr.fromJson[EmptyListMap].toOption.get == expectedObj, expectedObj.toJson == expectedStr) - } + assertTrue(jsonStr.fromJson[OptionalField].isLeft) + } @@ TestAspect.ignore ) ) )