From 23febad4c14872549e118158f701696f8067507d Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Mon, 30 Dec 2024 16:05:30 +0100 Subject: [PATCH] Fix the stack overflow on creation of JSON codecs for recursive data structures (#760) * Fix stack overflow on creation of JSON codecs for recursive data structures * Fix formatting --- .../main/scala/zio/schema/codec/JsonCodec.scala | 6 ++---- .../scala/zio/schema/codec/JsonCodecSpec.scala | 15 ++++++++++++++- 2 files changed, 16 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 b4df65f68..58c0b1525 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 @@ -341,7 +341,7 @@ object JsonCodec { case Schema.GenericRecord(_, structure, _) => recordEncoder(structure.toChunk, cfg) case Schema.Either(left, right, _) => ZJsonEncoder.either(schemaEncoder(left, cfg, discriminatorTuple), schemaEncoder(right, cfg, discriminatorTuple)) case Schema.Fallback(left, right, _, _) => fallbackEncoder(schemaEncoder(left, cfg, discriminatorTuple), schemaEncoder(right, cfg, discriminatorTuple)) - case l @ Schema.Lazy(_) => schemaEncoder(l.schema, cfg, discriminatorTuple) + case l @ Schema.Lazy(_) => ZJsonEncoder.suspend(schemaEncoder(l.schema, cfg, discriminatorTuple)) case s: Schema.Record[A] => caseClassEncoder(s, cfg, discriminatorTuple) case e @ Schema.Enum1(_, c, _) => enumEncoder(e, cfg, c) case e @ Schema.Enum2(_, c1, c2, _) => enumEncoder(e, cfg, c1, c2) @@ -725,7 +725,7 @@ object JsonCodec { case Schema.GenericRecord(_, structure, _) => recordDecoder(structure.toChunk, schema.annotations.contains(rejectExtraFields())) case Schema.Either(left, right, _) => ZJsonDecoder.either(schemaDecoder(left, -1), schemaDecoder(right, -1)) case s @ Schema.Fallback(_, _, _, _) => fallbackDecoder(s) - case l @ Schema.Lazy(_) => schemaDecoder(l.schema, discriminator) + case l @ Schema.Lazy(_) => ZJsonDecoder.suspend(schemaDecoder(l.schema, discriminator)) //case Schema.Meta(_, _) => astDecoder case s @ Schema.CaseClass0(_, _, _) => caseClass0Decoder(discriminator, s) case s @ Schema.CaseClass1(_, _, _, _) => caseClass1Decoder(discriminator, s) @@ -1094,10 +1094,8 @@ object JsonCodec { JsonError.Message("Fallback decoder was unable to decode both left and right sides") :: trace ) } - } } - } //scalafmt: { maxColumn = 400, optIn.configStyleArguments = false } 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 b6dd57558..568f75c89 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 @@ -1461,7 +1461,14 @@ object JsonCodecSpec extends ZIOSpecDefault { }, test("all optional fields empty") { assertEncodesThenDecodes(AllOptionalFields.schema, AllOptionalFields(None, None, None)) - } + }, + test("recursive data structure")( + assertDecodes( + Schema[Recursive], + Recursive(Some(Recursive(None))), + charSequenceToByteChunk("""{"n":{"n":null}}""") + ) + ) ), suite("record")( test("any") { @@ -2368,4 +2375,10 @@ object JsonCodecSpec extends ZIOSpecDefault { case class Case23(value: String) extends Enum23Cases } + + case class Recursive(n: Option[Recursive] = None) + + object Recursive { + implicit val schema: Schema[Recursive] = DeriveSchema.gen + } }