From 0128a1592bfc8ec51840d80c3d564d6241445385 Mon Sep 17 00:00:00 2001 From: Juliano Alves Date: Fri, 13 Sep 2024 17:48:45 +0100 Subject: [PATCH] generate empty collections for missing fields --- .../scala/zio/schema/codec/JsonCodec.scala | 17 ++++++++--- .../zio/schema/codec/JsonCodecSpec.scala | 29 +++++++++++++++++++ .../src/main/scala/zio/schema/Schema.scala | 10 ++++++- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/zio-schema-json/shared/src/main/scala/zio/schema/codec/JsonCodec.scala b/zio-schema-json/shared/src/main/scala/zio/schema/codec/JsonCodec.scala index ee9c14a3d..19aa3fc78 100644 --- a/zio-schema-json/shared/src/main/scala/zio/schema/codec/JsonCodec.scala +++ b/zio-schema-json/shared/src/main/scala/zio/schema/codec/JsonCodec.scala @@ -1523,12 +1523,21 @@ object JsonCodec { var i = 0 while (i < len) { if (buffer(i) == null) { - - if ((fields(i).optional || fields(i).transient) && fields(i).defaultValue.isDefined) + if ((fields(i).optional || fields(i).transient) && fields(i).defaultValue.isDefined) { buffer(i) = fields(i).defaultValue.get - else - buffer(i) = schemaDecoder(schemas(i)).unsafeDecodeMissing(spans(i) :: trace) + } else { + val schema = fields(i).schema match { + case l @ Schema.Lazy(_) => l.schema + case _ => schemas(i) + } + schema match { + case collection: Schema.Collection[_, _] => + buffer(i) = collection.empty + case _ => + buffer(i) = schemaDecoder(schema).unsafeDecodeMissing(spans(i) :: trace) + } + } } i += 1 } diff --git a/zio-schema-json/shared/src/test/scala/zio/schema/codec/JsonCodecSpec.scala b/zio-schema-json/shared/src/test/scala/zio/schema/codec/JsonCodecSpec.scala index 0d3e37175..65938a6ab 100644 --- a/zio-schema-json/shared/src/test/scala/zio/schema/codec/JsonCodecSpec.scala +++ b/zio-schema-json/shared/src/test/scala/zio/schema/codec/JsonCodecSpec.scala @@ -1051,6 +1051,29 @@ object JsonCodecSpec extends ZIOSpecDefault { ) } ), + suite("Missing collection fields")( + test("map") { + assertDecodes( + Schema[ListAndMap], + ListAndMap(Nil, Map.empty), + charSequenceToByteChunk("""{"list":[]}""") + ) + }, + test("list") { + assertDecodes( + Schema[ListAndMap], + ListAndMap(Nil, Map.empty), + charSequenceToByteChunk("""{"map":{}}""") + ) + }, + test("set") { + assertDecodes( + Schema[SetWrapper], + SetWrapper(Set.empty), + charSequenceToByteChunk("""{}""") + ) + } + ), suite("zio.json.ast.Json decoding")( test("Json.Obj") { assertDecodes( @@ -2172,6 +2195,12 @@ object JsonCodecSpec extends ZIOSpecDefault { implicit lazy val schema: Schema[ListAndMapAndOption] = DeriveSchema.gen[ListAndMapAndOption] } + final case class SetWrapper(set: Set[String]) + + object SetWrapper { + implicit lazy val schema: Schema[SetWrapper] = DeriveSchema.gen[SetWrapper] + } + final case class KeyWrapper(key: String) object KeyWrapper { diff --git a/zio-schema/shared/src/main/scala/zio/schema/Schema.scala b/zio-schema/shared/src/main/scala/zio/schema/Schema.scala index 59a0f617f..5fa6cf310 100644 --- a/zio-schema/shared/src/main/scala/zio/schema/Schema.scala +++ b/zio-schema/shared/src/main/scala/zio/schema/Schema.scala @@ -512,6 +512,7 @@ object Schema extends SchemaPlatformSpecific with SchemaEquality { sealed trait Collection[Col, Elem] extends Schema[Col] { def fromChunk: Chunk[Elem] => Col def toChunk: Col => Chunk[Elem] + def empty: Col } final case class Sequence[Col, Elem, I]( @@ -534,6 +535,7 @@ object Schema extends SchemaPlatformSpecific with SchemaEquality { override def toString: String = s"Sequence($elementSchema, $identity)" + override def empty: Col = Seq.empty[Elem].asInstanceOf[Col] } final case class Transform[A, B, I]( @@ -840,6 +842,7 @@ object Schema extends SchemaPlatformSpecific with SchemaEquality { override val toChunk: scala.collection.immutable.Map[K, V] => Chunk[(K, V)] = map => Chunk.fromIterable(map.toList) + override def empty: scala.collection.immutable.Map[K, V] = scala.collection.immutable.Map.empty[K, V] } final case class NonEmptyMap[K, V]( @@ -874,6 +877,8 @@ object Schema extends SchemaPlatformSpecific with SchemaEquality { override def makeAccessors(b: AccessorBuilder): b.Traversal[prelude.NonEmptyMap[K, V], (K, V)] = b.makeTraversal(self, keySchema <*> valueSchema) + + override def empty: prelude.NonEmptyMap[K, V] = throw new IllegalArgumentException(s"NonEmptySequence $identity cannot be empty") } final case class NonEmptySequence[Col, Elm, I]( @@ -900,6 +905,8 @@ object Schema extends SchemaPlatformSpecific with SchemaEquality { override def makeAccessors(b: AccessorBuilder): b.Traversal[Col, Elm] = b.makeTraversal(self, elementSchema) override def toString: String = s"NonEmptySequence($elementSchema, $identity)" + + override def empty: Col = throw new IllegalArgumentException(s"NonEmptySequence $identity cannot be empty") } final case class Set[A](elementSchema: Schema[A], override val annotations: Chunk[Any] = Chunk.empty) @@ -923,6 +930,7 @@ object Schema extends SchemaPlatformSpecific with SchemaEquality { override val toChunk: scala.collection.immutable.Set[A] => Chunk[A] = Chunk.fromIterable(_) + override def empty: scala.collection.immutable.Set[A] = scala.collection.immutable.Set.empty[A] } final case class Dynamic(override val annotations: Chunk[Any] = Chunk.empty) extends Schema[DynamicValue] { @@ -10240,4 +10248,4 @@ object Schema extends SchemaPlatformSpecific with SchemaEquality { type Field22 = F22 } } -} +} \ No newline at end of file