Skip to content

Commit

Permalink
Fixes #691 - when decoding json payload using decoder derived from a … (
Browse files Browse the repository at this point in the history
#693)

* Fixes #691 - when decoding json payload using decoder derived from a schema, the missing fields in are populated using their default values (for case classes with more than 22 fields)

* Fixes #691 - when decoding json payload using decoder derived from a schema, the missing fields in are populated using their default values (for case classes with more than 22 fields)

* #691 - formatting

* #691 - formatting

* #691 - fix tests in JsonCodecSpec
  • Loading branch information
stanislav-chetvertkov authored Jun 26, 2024
1 parent 3feff56 commit e3ede68
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ object JsonCodecJVMSpec extends ZIOSpecDefault {
) @@ TestAspect.jvmOnly @@ timeout(180.seconds)

private val decoderSuite = suite("decoding")(
suite("decode record with more than 22 fields")(
test("missing fields in the json payload are populated with their default values") {
val exampleSchema = zio.schema.DeriveSchema.gen[RecordExample]
val string = """{"f1": "test"}"""
assertDecodesJson(exampleSchema, RecordExample(Some("test")), string)
},
test("fail if a field with no default value is missing in the json payload") {
val exampleSchema = zio.schema.DeriveSchema.gen[RecordExample2]
val string = """{"f1": "test"}"""
assertDecodesJsonFailure(exampleSchema, string)
}
),
suite("case class")(
test("case class with empty option field is decoded by stream") {
val names = Gen.option(Gen.elements("John", "Jane", "Jermaine", "Jasmine"))
Expand Down Expand Up @@ -50,6 +62,16 @@ object JsonCodecJVMSpec extends ZIOSpecDefault {
)
)

private def assertDecodesJson[A](schema: Schema[A], value: A, jsonString: String) = {
val either = JsonCodec.jsonDecoder(schema).decodeJson(jsonString)
zio.test.assert(either)(isRight(equalTo(value)))
}

private def assertDecodesJsonFailure[A](schema: Schema[A], jsonString: String) = {
val either = JsonCodec.jsonDecoder(schema).decodeJson(jsonString)
zio.test.assertTrue(either.isLeft)
}

private def assertDecodesJsonStream[A](
schema: Schema[A],
value: Chunk[A],
Expand All @@ -64,4 +86,57 @@ object JsonCodecJVMSpec extends ZIOSpecDefault {
.either
assertZIO(result)(isRight(equalTo(value)))
}

case class RecordExample(
f1: Option[String],
f2: Option[String] = None,
f3: Option[String] = None,
f4: Option[String] = None,
f5: Option[String] = None,
f6: Option[String] = None,
f7: Option[String] = None,
f8: Option[String] = None,
f9: Option[String] = None,
f10: Option[String] = None,
f11: Option[String] = None,
f12: Option[String] = None,
f13: Option[String] = None,
f14: Option[String] = None,
f15: Option[String] = None,
f16: Option[String] = None,
f17: Option[String] = None,
f18: Option[String] = None,
f19: Option[String] = None,
f20: Option[String] = None,
f21: Option[String] = None,
f22: Option[String] = None,
f23: Option[String] = None
)

case class RecordExample2(
f1: Option[String],
f2: Option[String],
f3: Option[String] = None,
f4: Option[String] = None,
f5: Option[String] = None,
f6: Option[String] = None,
f7: Option[String] = None,
f8: Option[String] = None,
f9: Option[String] = None,
f10: Option[String] = None,
f11: Option[String] = None,
f12: Option[String] = None,
f13: Option[String] = None,
f14: Option[String] = None,
f15: Option[String] = None,
f16: Option[String] = None,
f17: Option[String] = None,
f18: Option[String] = None,
f19: Option[String] = None,
f20: Option[String] = None,
f21: Option[String] = None,
f22: Option[String] = None,
f23: Option[String] = None
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,18 @@ object JsonCodec {
()
}
}
(ListMap.newBuilder[String, Any] ++= builder.result()).result()
val tuples = builder.result()
val collectedFields: Set[String] = tuples.map { case (fieldName, _) => fieldName }.toSet
val resultBuilder = ListMap.newBuilder[String, Any]

// add fields with default values if they are not present in the JSON
structure.foreach { field =>
if (!collectedFields.contains(field.name) && field.optional && field.defaultValue.isDefined) {
val value = field.name -> field.defaultValue.get
resultBuilder += value
}
}
(resultBuilder ++= tuples).result()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,13 @@ object JsonCodecSpec extends ZIOSpecDefault {
}
),
suite("record")(
test("missing fields should be replaced by default values") {
assertDecodes(
recordSchema,
ListMap[String, Any]("foo" -> "s", "bar" -> 1),
charSequenceToByteChunk("""{"foo":"s"}""")
)
},
test("of primitives") {
assertEncodes(
recordSchema,
Expand Down Expand Up @@ -435,6 +442,13 @@ object JsonCodecSpec extends ZIOSpecDefault {
ListMap[String, Any]("foo" -> "s", "bar" -> 1),
charSequenceToByteChunk("""{"foo":"s","bar":1,"baz":2}""")
)
},
test("with missing fields") {
assertDecodes(
RecordExample.schema,
RecordExample(f1 = Some("test"), f2 = None),
charSequenceToByteChunk("""{"f1":"test"}""")
)
}
),
suite("transform")(
Expand Down Expand Up @@ -1636,6 +1650,7 @@ object JsonCodecSpec extends ZIOSpecDefault {
.Field(
"bar",
Schema.Primitive(StandardType.IntType),
annotations0 = Chunk(fieldDefaultValue(1)),
get0 = (p: ListMap[String, _]) => p("bar").asInstanceOf[Int],
set0 = (p: ListMap[String, _], v: Int) => p.updated("bar", v)
)
Expand Down Expand Up @@ -1867,4 +1882,35 @@ object JsonCodecSpec extends ZIOSpecDefault {
object AllOptionalFields {
implicit lazy val schema: Schema[AllOptionalFields] = DeriveSchema.gen[AllOptionalFields]
}

case class RecordExample(
f1: Option[String], // the only field that does not have a default value
f2: Option[String] = None,
f3: Option[String] = None,
f4: Option[String] = None,
f5: Option[String] = None,
f6: Option[String] = None,
f7: Option[String] = None,
f8: Option[String] = None,
f9: Option[String] = None,
f10: Option[String] = None,
f11: Option[String] = None,
f12: Option[String] = None,
f13: Option[String] = None,
f14: Option[String] = None,
f15: Option[String] = None,
f16: Option[String] = None,
f17: Option[String] = None,
f18: Option[String] = None,
f19: Option[String] = None,
f20: Option[String] = None,
f21: Option[String] = None,
f22: Option[String] = None,
f23: Option[String] = None
)

object RecordExample {
implicit lazy val schema: Schema[RecordExample] = DeriveSchema.gen[RecordExample]
}

}

0 comments on commit e3ede68

Please sign in to comment.