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

JsonCodec.jsonDecoder does not seem to work with schemas derived from case classes with more than 22 fields #691

Closed
stanislav-chetvertkov opened this issue Jun 10, 2024 · 1 comment

Comments

@stanislav-chetvertkov
Copy link
Contributor

JsonCodec.jsonDecoder does not seem to work with schemas derived from case classes with more than 22 fields,
in the following example

import zio.Scope
import zio.json.JsonDecoder
import zio.schema.codec.JsonCodec
import zio.schema.{DeriveSchema, Schema}
import zio.test._

import scala.annotation.nowarn

@nowarn("msg=missing interpolator")
object F22Spec extends ZIOSpecDefault {

  case class Example(
    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
                        )

  implicit val exampleSchema: Schema[Example] = DeriveSchema.gen[Example]

  override def spec: Spec[TestEnvironment with Scope, Any] = {
    suite("F22")(
      test("should work with more than 22 fields") {
        import zio.json.yaml.DecoderYamlOps

        implicit val decoder: JsonDecoder[Example] = JsonCodec.jsonDecoder(exampleSchema)

        val string = """f1: test"""

        string.fromYaml[Example] match {
          case Left(error) =>
            TestResult(TestArrow.make(_ => TestTrace.fail(ErrorMessage.text(error))))
          case Right(value) =>
            print(value)
            assertTrue(true)
        }
      }
    )
  }
}

It returns Example(Some(test),None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None)

but when I uncomment f23: Option[String] = None I get (Field f2 is missing) error
It could be due to some inconsistencies handling CaseClass22 and GenericRecord (for 23 or more fields) schemas

@stanislav-chetvertkov
Copy link
Contributor Author

the same behaviour also appears when using

import zio.json.DecoderOps
...
string.fromJson[Example]

I was able to make it work by changing recordDecoder method in zio.schema.codec.JsonCodec to look like this

    private def recordDecoder[Z](structure: Seq[Schema.Field[Z, _]]): ZJsonDecoder[ListMap[String, Any]] = {
      (trace: List[JsonError], in: RetractReader) => {
        val builder: ChunkBuilder[(String, Any)] = zio.ChunkBuilder.make[(String, Any)](structure.size)
        Lexer.char(trace, in, '{')
        if (Lexer.firstField(trace, in)) {
          while ( {
            val field = Lexer.string(trace, in).toString
            structure.find(_.name == field) match {
              case Some(Schema.Field(label, schema, _, _, _, _)) =>
                val trace_ = JsonError.ObjectAccess(label) :: trace
                Lexer.char(trace_, in, ':')
                val value = schemaDecoder(schema).unsafeDecode(trace_, in)
                builder += ((JsonFieldDecoder.string.unsafeDecodeField(trace_, label), value))
              case None =>
                Lexer.char(trace, in, ':')
                Lexer.skipValue(trace, in)

            }
            (Lexer.nextField(trace, in))
          }) {
            ()
          }
        }
        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)) {
            val value = field.name -> field.defaultValue.get
            resultBuilder += value
          }
        }
        (resultBuilder ++= tuples).result()
      }
    }

so that it adds fields with their default values from the schema if they were not present in the input JSON

I'm going create a pr containing the change soon

stanislav-chetvertkov added a commit to stanislav-chetvertkov/zio-schema that referenced this issue Jun 17, 2024
… a schema, the missing fields in are populated using their default values (for case classes with more than 22 fields)
stanislav-chetvertkov added a commit to stanislav-chetvertkov/zio-schema that referenced this issue Jun 26, 2024
… a schema, the missing fields in are populated using their default values (for case classes with more than 22 fields)
stanislav-chetvertkov added a commit to stanislav-chetvertkov/zio-schema that referenced this issue Jun 26, 2024
stanislav-chetvertkov added a commit to stanislav-chetvertkov/zio-schema that referenced this issue Jun 26, 2024
stanislav-chetvertkov added a commit to stanislav-chetvertkov/zio-schema that referenced this issue Jun 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant