diff --git a/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala b/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala index 3b9e62edf..369bb1e89 100644 --- a/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala +++ b/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala @@ -398,6 +398,13 @@ object DeriveJsonDecoder { val names = ctx.subtypes.map { p => p.annotations.collectFirst { case jsonHint(name) => name }.getOrElse(jsonHintFormat(p.typeName.short)) }.toArray + if (names.distinct.length != names.length) { + val collisions = names.groupBy(identity).collect { case (n, ns) if ns.lengthCompare(1) > 0 => n } + throw new AssertionError( + s"Case names in ADT ${ctx.typeName.full} must be distinct, " + + s"name(s) ${collisions.mkString(",")} are duplicated" + ) + } val matrix = new StringMatrix(names) lazy val tcs = ctx.subtypes.map(_.typeclass).toArray.asInstanceOf[Array[JsonDecoder[Any]]] lazy val namesMap = names.zipWithIndex.toMap diff --git a/zio-json/shared/src/main/scala-3/zio/json/macros.scala b/zio-json/shared/src/main/scala-3/zio/json/macros.scala index 276e5c36a..99f857f77 100644 --- a/zio-json/shared/src/main/scala-3/zio/json/macros.scala +++ b/zio-json/shared/src/main/scala-3/zio/json/macros.scala @@ -404,6 +404,11 @@ sealed class JsonDecoderDerivation(config: JsonCodecConfiguration) extends Deriv val names: Array[String] = IArray.genericWrapArray(ctx.subtypes.map { p => p.annotations.collectFirst { case jsonHint(name) => name }.getOrElse(jsonHintFormat(p.typeInfo.short)) }).toArray + if (names.distinct.length != names.length) { + val collisions = names.groupBy(identity).collect { case (n, ns) if ns.lengthCompare(1) > 0 => n } + throw new AssertionError(s"Case names in ADT ${ctx.typeInfo.full} must be distinct, " + + s"name(s) ${collisions.mkString(",")} are duplicated") + } val matrix: StringMatrix = new StringMatrix(names) lazy val tcs: Array[JsonDecoder[Any]] = IArray.genericWrapArray(ctx.subtypes.map(_.typeclass)).toArray.asInstanceOf[Array[JsonDecoder[Any]]] diff --git a/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala b/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala index 37be1314d..7afaeaa80 100644 --- a/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala @@ -282,6 +282,20 @@ object DecoderSpec extends ZIOSpecDefault { assert("""{"hint":"Child2"}""".fromJson[Parent])(isLeft(equalTo("(invalid disambiguator)"))) && assert("""{"child1":{}}""".fromJson[Parent])(isLeft(equalTo("(missing hint 'hint')"))) }, + test("sum with duplicated case names") { + for { + error <- ZIO.attempt { + sealed trait Fruit + case class Banana(curvature: Double) extends Fruit + @jsonHint("Banana") case class Apple(color: String) extends Fruit + DeriveJsonDecoder.gen[Fruit] + }.flip + } yield assertTrue( + error.getMessage.matches( + """Case names in ADT zio.json.DecoderSpec.spec(.\$anonfun)?.Fruit must be distinct, name\(s\) Banana are duplicated""" + ) + ) + }, test("unicode") { assert(""""€🐵🥰"""".fromJson[String])(isRight(equalTo("€🐵🥰"))) },