From 0128a1592bfc8ec51840d80c3d564d6241445385 Mon Sep 17 00:00:00 2001 From: Juliano Alves Date: Fri, 13 Sep 2024 17:48:45 +0100 Subject: [PATCH 1/3] 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 From 3abbd4e543da10be610d40879a42031bfe7d977a Mon Sep 17 00:00:00 2001 From: Juliano Alves Date: Fri, 13 Sep 2024 18:01:31 +0100 Subject: [PATCH 2/3] fix formatting and exception --- zio-schema/shared/src/main/scala/zio/schema/Schema.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 5fa6cf310..57ec401aa 100644 --- a/zio-schema/shared/src/main/scala/zio/schema/Schema.scala +++ b/zio-schema/shared/src/main/scala/zio/schema/Schema.scala @@ -878,7 +878,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") + override def empty: prelude.NonEmptyMap[K, V] = + throw new IllegalArgumentException("NonEmptyMap cannot be empty") } final case class NonEmptySequence[Col, Elm, I]( @@ -10248,4 +10249,4 @@ object Schema extends SchemaPlatformSpecific with SchemaEquality { type Field22 = F22 } } -} \ No newline at end of file +} From 64050bd23641909a988ae09a48fda690ad40056f Mon Sep 17 00:00:00 2001 From: Juliano Alves Date: Tue, 17 Sep 2024 11:14:32 +0100 Subject: [PATCH 3/3] correct implementation of empty for Sequence --- .../zio/schema/codec/JsonCodecSpec.scala | 34 ++++++++++++++++--- .../src/main/scala/zio/schema/Schema.scala | 2 +- 2 files changed, 31 insertions(+), 5 deletions(-) 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 65938a6ab..b6dd57558 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 @@ -1054,15 +1054,15 @@ object JsonCodecSpec extends ZIOSpecDefault { suite("Missing collection fields")( test("map") { assertDecodes( - Schema[ListAndMap], - ListAndMap(Nil, Map.empty), + Schema[ListAndMapAndOption], + ListAndMapAndOption(Nil, Map.empty, None), charSequenceToByteChunk("""{"list":[]}""") ) }, test("list") { assertDecodes( - Schema[ListAndMap], - ListAndMap(Nil, Map.empty), + Schema[ListAndMapAndOption], + ListAndMapAndOption(Nil, Map.empty, None), charSequenceToByteChunk("""{"map":{}}""") ) }, @@ -1072,6 +1072,20 @@ object JsonCodecSpec extends ZIOSpecDefault { SetWrapper(Set.empty), charSequenceToByteChunk("""{}""") ) + }, + test("vector") { + assertDecodes( + Schema[VectorWrapper], + VectorWrapper(Vector.empty), + charSequenceToByteChunk("""{}""") + ) + }, + test("chunck") { + assertDecodes( + Schema[ChunckWrapper], + ChunckWrapper(Chunk.empty), + charSequenceToByteChunk("""{}""") + ) } ), suite("zio.json.ast.Json decoding")( @@ -2201,6 +2215,18 @@ object JsonCodecSpec extends ZIOSpecDefault { implicit lazy val schema: Schema[SetWrapper] = DeriveSchema.gen[SetWrapper] } + final case class VectorWrapper(sequence: Vector[String]) + + object VectorWrapper { + implicit lazy val schema: Schema[VectorWrapper] = DeriveSchema.gen[VectorWrapper] + } + + final case class ChunckWrapper(chunk: Chunk[String]) + + object ChunckWrapper { + implicit lazy val schema: Schema[ChunckWrapper] = DeriveSchema.gen[ChunckWrapper] + } + 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 57ec401aa..56763d16e 100644 --- a/zio-schema/shared/src/main/scala/zio/schema/Schema.scala +++ b/zio-schema/shared/src/main/scala/zio/schema/Schema.scala @@ -535,7 +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] + override def empty: Col = fromChunk(Chunk.empty[Elem]) } final case class Transform[A, B, I](