From 116b786fdd49ce8c0faffba618577e91493b3370 Mon Sep 17 00:00:00 2001 From: Jisoo Park Date: Thu, 4 Jul 2024 12:31:13 +0900 Subject: [PATCH] Fix JSON decoding of empty objects - discriminated case object - case object with @rejectExtraField --- .../scala/zio/schema/codec/JsonCodec.scala | 24 ++++++++++++++++++- .../zio/schema/codec/JsonCodecSpec.scala | 20 +++++++++++++++- 2 files changed, 42 insertions(+), 2 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 b72d8af6d..f81e15630 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 @@ -991,7 +991,29 @@ object JsonCodec { import JsonCodec.JsonDecoder.schemaDecoder private[codec] def caseClass0Decoder[Z](discriminator: Int, schema: Schema.CaseClass0[Z]): ZJsonDecoder[Z] = { (trace: List[JsonError], in: RetractReader) => - if (discriminator == -1) Codecs.unitDecoder.unsafeDecode(trace, in) + val rejectExtraFields = schema.annotations.collectFirst({ case _: rejectExtraFields => () }).isDefined + var i = 0 + def skipField(): Unit = { + if (rejectExtraFields) { + throw UnsafeJson(JsonError.Message("extra field") :: trace) + } + Lexer.char(trace, in, '"') + Lexer.skipString(trace, in) + Lexer.char(trace, in, ':') + Lexer.skipValue(trace, in) + i += 1 + } + + if (discriminator == -2) { + while (Lexer.nextField(trace, in)) { skipField() } + } else { + if (discriminator == -1) { Lexer.char(trace, in, '{') } + if (Lexer.firstField(trace, in)) { + skipField() + while (Lexer.nextField(trace, in)) { skipField() } + } + } + schema.defaultConstruct() } diff --git a/zio-schema-json/shared/src/test/scala-2/zio/schema/codec/JsonCodecSpec.scala b/zio-schema-json/shared/src/test/scala-2/zio/schema/codec/JsonCodecSpec.scala index 115bee8e5..11f7b848b 100644 --- a/zio-schema-json/shared/src/test/scala-2/zio/schema/codec/JsonCodecSpec.scala +++ b/zio-schema-json/shared/src/test/scala-2/zio/schema/codec/JsonCodecSpec.scala @@ -524,7 +524,12 @@ object JsonCodecSpec extends ZIOSpecDefault { PersonWithRejectExtraFields.schema, """{"name":"test","age":10,"extraField":10}""", JsonError.Message("extra field") :: Nil - ) + ) &> + assertDecodesToError( + CaseClass0WithRejectExtraFields.schema, + """{"extraField":10}""", + JsonError.Message("extra field") :: Nil + ) }, test("transient field annotation") { assertDecodes( @@ -1242,6 +1247,13 @@ object JsonCodecSpec extends ZIOSpecDefault { assertEncodesThenDecodes(Schema[Command], Command.Cash) &> assertEncodesThenDecodes(Schema[Command], Command.Buy(100)) }, + test("decode discriminated case objects in array")( + assertDecodes(Schema[List[Command]], Command.Cash :: Nil, charSequenceToByteChunk("""[{"type":"Cash"}]""")) + ), + test("decode discriminated case objects with extra fields")( + assertDecodes(Schema[Command], Command.Cash, charSequenceToByteChunk("""{"type":"Cash","extraField":1}""")) &> + assertDecodes(Schema[Command], Command.Cash, charSequenceToByteChunk("""{"extraField":1,"type":"Cash"}"""")) + ), suite("of case objects")( test("without annotation")( assertEncodesThenDecodes(Schema[Color], Color.Red) @@ -1593,6 +1605,12 @@ object JsonCodecSpec extends ZIOSpecDefault { val schema: Schema[PersonWithRejectExtraFields] = DeriveSchema.gen[PersonWithRejectExtraFields] } + + @rejectExtraFields final case class CaseClass0WithRejectExtraFields() + object CaseClass0WithRejectExtraFields { + val schema: Schema[CaseClass0WithRejectExtraFields] = DeriveSchema.gen[CaseClass0WithRejectExtraFields] + } + case class FieldDefaultValueSearchRequest( query: String, pageNumber: Int,