Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #691 - when decoding json payload using decoder derived from a … #693

Merged
merged 5 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
}

}
Loading