Remove magnolia dependency for Scala 3, use native macros - #1565
Conversation
c989e9c to
26dcdf2
Compare
There was a problem hiding this comment.
Pull request overview
Updates Scala 3 JSON codec derivation to use native Scala 3 macros (removing the Scala 3 Magnolia dependency), and reorganizes the test suite to avoid macro/test-data cyclic dependencies while preserving existing behavior.
Changes:
- Replaced Scala 3 Magnolia-based derivation with native Scala 3 macro/inline derivation in
macros.scala. - Moved/renamed test packages to
zio.jsontestand split Scala 2 vs Scala 3 test sources to support cross-compilation. - Updated build and CI settings (removed Magnolia for Scala 3, added
zioJsonTestDatamodule, increased thread stack size).
Reviewed changes
Copilot reviewed 38 out of 43 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| zio-json/shared/src/test/scala/zio/json/internal/StringMatrixSpec.scala | Moves test package to zio.jsontest.* and imports zio.json.internal. |
| zio-json/shared/src/test/scala/zio/json/internal/SafeNumbersSpec.scala | Moves test package to zio.jsontest.*; updates generator imports. |
| zio-json/shared/src/test/scala/zio/json/ast/JsonSpec.scala | Moves test package to zio.jsontest.*; adds imports for zio.json + AST. |
| zio-json/shared/src/test/scala/zio/json/RoundTripSpec.scala | Moves test package to zio.jsontest.* and adjusts imports. |
| zio-json/shared/src/test/scala/zio/json/JavaTimeSpec.scala | Moves test package to zio.jsontest.* and adjusts imports. |
| zio-json/shared/src/test/scala/zio/json/Gens.scala | Moves test generators to zio.jsontest package. |
| zio-json/shared/src/test/scala/zio/json/EncoderSpec.scala | Moves test package to zio.jsontest.* and adjusts imports. |
| zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala | Moves test package to zio.jsontest.*; loosens regex expectations for class names. |
| zio-json/shared/src/test/scala/zio/json/ConfigurableDeriveCodecSpec.scala | Moves test package to zio.jsontest.* and adjusts imports. |
| zio-json/shared/src/test/scala/zio/json/CodecSpec.scala | Moves test package to zio.jsontest.* and adjusts imports. |
| zio-json/shared/src/test/scala/zio/json/CarterSpec.scala | Moves test package to zio.jsontest.* and adjusts imports. |
| zio-json/shared/src/test/scala/zio/json/AnnotationsCodecSpec.scala | Moves test package to zio.jsontest.* and adjusts imports. |
| zio-json/shared/src/test/scala-3/zio/json/EncoderVesionSpecificSpec.scala | Moves Scala 3-specific test package to zio.jsontest.*. |
| zio-json/shared/src/test/scala-3/zio/json/DecoderVersionSpecificSpec.scala | Moves Scala 3-specific test package to zio.jsontest.*. |
| zio-json/shared/src/test/scala-3/zio/json/CodecVersionSpecificSpec.scala | Moves Scala 3-specific test package to zio.jsontest.*. |
| zio-json/shared/src/test/scala-2.x/zio/json/DecoderSpec.scala | Adds Scala 2-only copy of DecoderSpec under Scala-2.x sources. |
| zio-json/shared/src/test/scala-2.x/zio/json/ConfigurableDeriveCodecSpec.scala | Adds Scala 2-only copy of configurable derivation spec. |
| zio-json/shared/src/test/scala-2.x/zio/json/CodecSpec.scala | Adds Scala 2-only copy of codec spec. |
| zio-json/shared/src/test/scala-2.x/zio/json/CarterSpec.scala | Adds Scala 2-only copy of Carter spec. |
| zio-json/shared/src/test/scala-2.x/zio/json/AnnotationsCodecSpec.scala | Adds Scala 2-only copy of annotation override tests. |
| zio-json/shared/src/test/scala-2.13/zio/json/EncoderVesionSpecificSpec.scala | Removes Scala 2.13-specific encoder test (moved to split structure). |
| zio-json/shared/src/test/scala-2.13/zio/json/DecoderVersionSpecificSpec.scala | Removes Scala 2.13-specific decoder test (moved to split structure). |
| zio-json/shared/src/test/scala-2.13/zio/json/CodecVersionSpecificSpec.scala | Removes Scala 2.13-specific codec test (moved to split structure). |
| zio-json/shared/src/main/scala-3/zio/json/macros.scala | Replaces Magnolia derivation with native Scala 3 macros + helper infrastructure. |
| zio-json/jvm/src/test/scala/zio/json/data/geojson/GeoJSON.scala | Updates hand-rolled decoders to implement JsonDecoder directly. |
| zio-json/jvm/src/test/scala/zio/json/TestUtils.scala | Moves JVM test utilities into zio.jsontest package. |
| zio-json/jvm/src/test/scala/zio/json/JsonTestSuiteSpec.scala | Moves JVM suite spec into zio.jsontest package and updates imports. |
| zio-json/jvm/src/test/scala/zio/json/EncoderPlatformSpecificSpec.scala | Updates imports to use moved TestUtils + moved DecoderSpec helpers. |
| zio-json/jvm/src/test/scala/zio/json/DecoderPlatformSpecificSpec.scala | Updates imports to use moved TestUtils. |
| zio-json/jvm/src/test/scala-3.x/zio/jsontest/EncoderPlatformSpecificSpec.scala | Adds Scala 3-only JVM encoder spec under new test package. |
| zio-json/jvm/src/test/scala-3.x/zio/jsontest/DecoderPlatformSpecificSpec.scala | Adds Scala 3-only JVM decoder spec under new test package. |
| zio-json/jvm/src/test/scala-2.x/zio/json/data/twitter/Twitter.scala | Adds Scala 2-only Twitter test-data models with circe + zio-json codecs. |
| zio-json/jvm/src/test/scala-2.x/zio/json/data/googlemaps/GoogleMaps.scala | Adds Scala 2-only Google Maps test-data models with circe + zio-json codecs. |
| zio-json/jvm/src/test/scala-2.x/zio/json/data/geojson/GeoJSON.scala | Adds Scala 2-only GeoJSON test-data (generated + hand-rolled) used by tests. |
| docs/index.mdx | Updates docs to describe Scala 2 Magnolia vs Scala 3 native macros. |
| build.sbt | Removes Scala 3 Magnolia dependency; adds zioJsonTestData project and stack/compilation settings. |
| README.md | Updates readme to describe Scala 2 Magnolia vs Scala 3 native macros. |
| .jvmopts | Increases JVM thread stack size to 8M. |
| .github/workflows/ci.yml | Increases CI JVM thread stack size to 8M. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for ((key, value) <- fields) { | ||
| namesMap.get(key) match { | ||
| case Some(field) => | ||
| val trace_ = JsonError.ObjectAccess(key) :: trace | ||
| if (defaults(field).isDefined) { | ||
| val opt = JsonDecoder.option(tcs(field)).unsafeFromJsonAST(trace_, value) | ||
| ps(field) = opt.getOrElse(defaults(field).get.apply()) | ||
| } else { | ||
| ps(field) = tcs(field).unsafeFromJsonAST(trace_, value) | ||
| } | ||
| case None => | ||
| if (no_extra) | ||
| throw UnsafeJson(JsonError.Message("invalid extra field") :: trace) | ||
| } | ||
| idx += 1 | ||
| } |
| case Json.Obj(fields) => Right(Json.Obj(fields :+ hintFieldName -> Json.Str(name))) | ||
| case _ => Left("Subtype is not encoded as an object") |
| case Some((_, Json.Str(name))) => | ||
| namesMap.get(name) match { | ||
| case Some(idx) => | ||
| tcs(idx).unsafeFromJsonAST(JsonError.ObjectAccess(name) :: trace, json).asInstanceOf[A] |
26dcdf2 to
e3ad01f
Compare
There was a problem hiding this comment.
Pull request overview
This PR replaces Magnolia-based Scala 3 derivation with native Scala 3 macro derivation for ZIO JSON codecs, while also reorganizing test code to avoid cyclic dependencies and updating build/CI settings to support the new macro expansion behavior.
Changes:
- Replaced Scala 3 codec derivation implementation in
macros.scala(encoder/decoder derivation, >64 fields support, ADT validation, enum handling). - Repackaged and split tests across Scala versions/platforms (introducing
zio.jsontestand a newzio-json-test-datasubproject). - Updated build configuration/documentation and increased JVM stack settings for macro-heavy compilation.
Reviewed changes
Copilot reviewed 42 out of 47 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| zio-json/shared/src/main/scala-3/zio/json/macros.scala | Rewrites Scala 3 derivation using Scala 3 macros and new helper structure. |
| zio-json/shared/src/test/scala/zio/json/internal/StringMatrixSpec.scala | Moves test package to zio.jsontest.internal and imports internal APIs. |
| zio-json/shared/src/test/scala/zio/json/internal/SafeNumbersSpec.scala | Moves test package to zio.jsontest.internal and updates gens import. |
| zio-json/shared/src/test/scala/zio/json/ast/JsonSpec.scala | Moves AST tests into zio.jsontest.ast package. |
| zio-json/shared/src/test/scala/zio/json/RoundTripSpec.scala | Moves test to zio.jsontest and updates imports. |
| zio-json/shared/src/test/scala/zio/json/JavaTimeSpec.scala | Moves test to zio.jsontest and updates imports. |
| zio-json/shared/src/test/scala/zio/json/Gens.scala | Moves generators to zio.jsontest. |
| zio-json/shared/src/test/scala/zio/json/EncoderSpec.scala | Moves test to zio.jsontest and updates imports. |
| zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala | Moves test to zio.jsontest and relaxes regex matching for error messages. |
| zio-json/shared/src/test/scala/zio/json/ConfigurableDeriveCodecSpec.scala | Moves test to zio.jsontest and updates imports. |
| zio-json/shared/src/test/scala/zio/json/CodecSpec.scala | Moves test to zio.jsontest and updates imports. |
| zio-json/shared/src/test/scala/zio/json/CarterSpec.scala | Moves test to zio.jsontest and updates imports. |
| zio-json/shared/src/test/scala/zio/json/AnnotationsCodecSpec.scala | Moves test to zio.jsontest and updates imports. |
| zio-json/shared/src/test/scala-3/zio/json/EncoderVesionSpecificSpec.scala | Scala 3 specific test moved to zio.jsontest. |
| zio-json/shared/src/test/scala-3/zio/json/DecoderVersionSpecificSpec.scala | Scala 3 specific test moved to zio.jsontest. |
| zio-json/shared/src/test/scala-3/zio/json/CodecVersionSpecificSpec.scala | Scala 3 specific test moved to zio.jsontest. |
| zio-json/shared/src/test/scala-2.x/zio/json/ConfigurableDeriveCodecSpec.scala | Adds Scala 2-specific copy of configurable derivation tests. |
| zio-json/shared/src/test/scala-2.x/zio/json/CodecSpec.scala | Adds Scala 2-specific copy of codec tests. |
| zio-json/shared/src/test/scala-2.x/zio/json/CarterSpec.scala | Adds Scala 2-specific copy of Carter regression tests. |
| zio-json/shared/src/test/scala-2.x/zio/json/AnnotationsCodecSpec.scala | Adds Scala 2-specific copy of annotation override tests. |
| zio-json/shared/src/test/scala-2.13/zio/json/EncoderVesionSpecificSpec.scala | Removes old Scala 2.13-only version-specific encoder spec. |
| zio-json/shared/src/test/scala-2.13/zio/json/DecoderVersionSpecificSpec.scala | Removes old Scala 2.13-only version-specific decoder spec. |
| zio-json/shared/src/test/scala-2.13/zio/json/CodecVersionSpecificSpec.scala | Removes old Scala 2.13-only version-specific codec spec. |
| zio-json/shared/src/test/scala-2.12-2.13/zio/json/internal/FieldEncoderHelperSpec.scala | Adds tests for new/updated FieldEncoder behavior. |
| zio-json/shared/src/test/scala-2.12-2.13/zio/json/codegen/GeneratorSpec.scala | Adds tests for JSON-to-case-class code generator. |
| zio-json/jvm/src/test/scala/zio/json/TestUtils.scala | Moves JVM test utils to zio.jsontest. |
| zio-json/jvm/src/test/scala/zio/json/JsonTestSuiteSpec.scala | Moves suite spec to zio.jsontest and updates imports. |
| zio-json/jvm/src/test/scala-3.x/zio/jsontest/EncoderPlatformSpecificSpec.scala | Adds Scala 3 JVM-specific encoder tests under zio.jsontest. |
| zio-json/jvm/src/test/scala-3.x/zio/jsontest/DecoderPlatformSpecificSpec.scala | Adds Scala 3 JVM-specific decoder tests under zio.jsontest. |
| zio-json/jvm/src/test/scala-2.x/zio/json/data/twitter/Twitter.scala | Adds Scala 2 test data models for Twitter. |
| zio-json/jvm/src/test/scala-2.x/zio/json/data/googlemaps/GoogleMaps.scala | Adds Scala 2 test data models for Google Maps. |
| zio-json/jvm/src/test/scala-2.x/zio/json/data/geojson/GeoJSON.scala | Adds Scala 2 test data models for GeoJSON (generated + handrolled). |
| zio-json/jvm/src/test/scala-2.x/zio/json/EncoderPlatformSpecificSpec.scala | Updates imports to use zio.jsontest.TestUtils and moved logEvent package. |
| zio-json/jvm/src/test/scala-2.x/zio/json/DecoderPlatformSpecificSpec.scala | Updates imports to use zio.jsontest.TestUtils. |
| zio-json/jvm/src/jmh/scala/zio/json/TwitterAPIBenchmarks.scala | Updates benchmarks to import moved TestUtils. |
| zio-json/jvm/src/jmh/scala/zio/json/SyntheticBenchmarks.scala | Updates benchmarks to import moved TestUtils. |
| zio-json/jvm/src/jmh/scala/zio/json/GoogleMapsAPIBenchmarks.scala | Updates benchmarks to import moved TestUtils. |
| zio-json/jvm/src/jmh/scala/zio/json/GeoJSONBenchmarks.scala | Updates benchmarks to import moved TestUtils. |
| zio-json-test-data/jvm/src/main/scala/zio/json/data/twitter/Twitter.scala | Introduces new test-data subproject Twitter models. |
| zio-json-test-data/jvm/src/main/scala/zio/json/data/googlemaps/GoogleMaps.scala | Introduces new test-data subproject Google Maps models. |
| zio-json-test-data/jvm/src/main/scala/zio/json/data/geojson/GeoJSON.scala | Adjusts handrolled decoders to use new JsonDecoder[...] (not AbstractJsonDecoder). |
| docs/index.mdx | Updates docs to reflect Scala 2 Magnolia vs Scala 3 native macros derivation. |
| README.md | Updates README to reflect Scala 2 Magnolia vs Scala 3 native macros derivation. |
| build.sbt | Removes Scala 3 Magnolia dep, adds zioJsonTestData project, updates aliases/classpaths, and expands MiMa filters. |
| .jvmopts | Increases stack size to -Xss8m. |
| .github/workflows/ci.yml | Increases CI stack size to -Xss8M. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private inline def summonDecoder[T]: JsonDecoder[T] = | ||
| summonFrom { | ||
| case d: JsonDecoder[T] => d | ||
| case m: Mirror.Of[T] => DeriveJsonDecoder.gen[T](using JsonCodecConfiguration.default)(using m) | ||
| } | ||
|
|
||
| @noinline private def nonZeroStateWrite(c: Char): Unit = { | ||
| if (c != ' ' && c != '\n') { | ||
| if (state == 2) { | ||
| if (c == '{') state = 1 | ||
| } else { | ||
| state = 0 | ||
| if (c != '}') { | ||
| out.write(',') | ||
| JsonEncoder.pad(indent, out) | ||
| } | ||
| out.write(c) | ||
| } | ||
| } | ||
| inline def summonEncoders[T <: Tuple]: List[JsonEncoder[?]] = | ||
| inline erasedValue[T] match { | ||
| case _: EmptyTuple => Nil | ||
| case _: (t *: ts) => summonEncoder[t] :: summonEncoders[ts] | ||
| } | ||
|
|
||
| @inline def write(s: String): Unit = | ||
| if (state == 0) out.write(s) | ||
| else nonZeroStateWrite(s) | ||
|
|
||
| @noinline private def nonZeroStateWrite(s: String): Unit = { | ||
| var i = 0 | ||
| while (i < s.length) { | ||
| val c = s.charAt(i) | ||
| if (c != ' ' && c != '\n') { | ||
| if (state == 2) { | ||
| if (c == '{') state = 1 | ||
| } else { | ||
| state = 0 | ||
| if (c != '}') { | ||
| out.write(',') | ||
| JsonEncoder.pad(indent, out) | ||
| } | ||
| while (i < s.length) { | ||
| out.write(s.charAt(i)) | ||
| i += 1 | ||
| } | ||
| return | ||
| } | ||
| } | ||
| i += 1 | ||
| private inline def summonEncoder[T]: JsonEncoder[T] = | ||
| summonFrom { | ||
| case e: JsonEncoder[T] => e | ||
| case m: Mirror.Of[T] => DeriveJsonEncoder.gen[T](using JsonCodecConfiguration.default)(using m) | ||
| } |
84bf068 to
1b93d01
Compare
There was a problem hiding this comment.
Pull request overview
This PR replaces the Scala 3 Magnolia-based derivation with native Scala 3 macros for JSON codec derivation, while updating build/test infrastructure and documentation to reflect the new derivation strategy.
Changes:
- Rewrote Scala 3 derivation in
macros.scalausing inline + quoted macros, including >64-field support and additional validation. - Removed the Scala 3
magnolia1_3dependency and updated build settings / MiMa filters accordingly. - Updated docs/tests to reflect new error-message patterns and Scala 3 macro-based derivation.
Reviewed changes
Copilot reviewed 7 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala | Makes error-message matching resilient to Scala-version-specific class-name mangling. |
| zio-json/shared/src/main/scala-3/zio/json/macros.scala | Replaces Magnolia derivation with Scala 3 macro+inline derivation; adds helpers and rewrites encoder/decoder derivation logic. |
| zio-json/jvm/src/test/scala/zio/json/data/geojson/GeoJSON.scala | Updates custom decoders to implement JsonDecoder directly. |
| docs/index.mdx | Updates derivation documentation to distinguish Scala 2 (Magnolia) vs Scala 3 (native macros). |
| build.sbt | Removes Scala 3 Magnolia dependency; adjusts scalacOptions scope and MiMa filters; adds JMH classpath tweak. |
| README.md | Updates derivation documentation to distinguish Scala 2 vs Scala 3. |
| .jvmopts | Increases thread stack size to support Scala 3 macro expansion. |
| .github/workflows/ci.yml | Aligns CI stack size (-Xss8M) with local build needs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| val opt = JsonDecoder.option(tcs(field)).unsafeDecode(trace_, in) | ||
| ps(field) = opt.getOrElse(defaults(field).get.apply()) |
| final class NestedWriter(out: Write, indent: Option[Int]) extends Write { | ||
| private[this] var first, second = true | ||
|
|
||
| def write(c: Char): Unit = write(c.toString) // could be optimised | ||
|
|
||
| def write(s: String): Unit = | ||
| if (first || second) { | ||
| var i = 0 | ||
| while (i < s.length) { | ||
| val c = s.charAt(i) | ||
| if (c == ' ' || c == '\n') {} | ||
| else if (first && c == '{') { | ||
| first = false | ||
| } else if (second) { | ||
| second = false | ||
| if (c != '}') { | ||
| out.write(',') | ||
| JsonEncoder.pad(indent, out) | ||
| } | ||
| return out.write(s.substring(i)) | ||
| } | ||
| i += 1 | ||
| } | ||
| } else out.write(s) |
|
|
||
| construct(ps) | ||
|
|
||
| case _ => throw UnsafeJson(JsonError.Message("Not an object") :: trace) |
6cf827c to
ee24653
Compare
There was a problem hiding this comment.
Pull request overview
This PR removes the Magnolia dependency for Scala 3 by replacing it with native Scala 3 macro-based derivation for zio-json encoders/decoders, while updating build/test setup and documentation to reflect the new approach.
Changes:
- Reimplemented Scala 3 JSON codec derivation using native macros (products, sums/enums, >64 fields support, validation).
- Updated/added Scala 2 & Scala 3 tests to cover derivation edge cases and explicit empty-collection behavior.
- Removed Scala 3 Magnolia dependency and adjusted build/CI JVM stack settings and docs.
Reviewed changes
Copilot reviewed 9 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
zio-json/shared/src/main/scala-3/zio/json/macros.scala |
Replaces Magnolia-based derivation with Scala 3 macro + inline Mirror derivation helpers. |
zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala |
Loosens regex expectations for macro error messages across Scala versions. |
zio-json/shared/src/test/scala-3/zio/json/EncoderVesionSpecificSpec.scala |
Adds Scala 3-only tests for explicit empty-collection encoding behavior. |
zio-json/shared/src/test/scala-3/zio/json/DecoderVersionSpecificSpec.scala |
Adds Scala 3-only tests for derivation edge cases and explicit empty-collection decoding behavior. |
zio-json/shared/src/test/scala-2.12-2.13/zio/json/internal/FieldEncoderHelperSpec.scala |
Adds Scala 2.12/2.13 tests for FieldEncoder skip behavior. |
zio-json/shared/src/test/scala-2.12-2.13/zio/json/codegen/GeneratorSpec.scala |
Adds Scala 2.12/2.13 tests for JSON-to-case-class codegen. |
zio-json/jvm/src/test/scala/zio/json/data/geojson/GeoJSON.scala |
Updates custom decoders to extend JsonDecoder directly. |
docs/index.mdx |
Updates docs to reflect Scala 2 Magnolia vs Scala 3 native macro derivation. |
README.md |
Updates README derivation description (Scala 2 vs Scala 3). |
build.sbt |
Removes Scala 3 Magnolia dependency, updates MiMa filters, adds JMH classpath tweak. |
.jvmopts |
Increases thread stack size to support Scala 3 macro inlining. |
.github/workflows/ci.yml |
Increases stack size in CI environment variables. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private inline def summonDecoder[T]: JsonDecoder[T] = | ||
| summonFrom { | ||
| case d: JsonDecoder[T] => d | ||
| case m: Mirror.Of[T] => DeriveJsonDecoder.gen[T](using JsonCodecConfiguration.default)(using m) | ||
| } |
| private inline def summonEncoder[T]: JsonEncoder[T] = | ||
| summonFrom { | ||
| case e: JsonEncoder[T] => e | ||
| case m: Mirror.Of[T] => DeriveJsonEncoder.gen[T](using JsonCodecConfiguration.default)(using m) | ||
| } |
| /** | ||
| * If used on a case class, will exit early if any fields are in the JSON that | ||
| * do not correspond to field names in the case class. | ||
| * If used on a case class or case class field, will encode `None` values as `null` and `Nil` values as `[]`. |
| test("without annotation, missing empty collections decode to defaults") { | ||
| implicit val codec: JsonCodec[WithoutExplicitEmpty] = DeriveJsonCodec.gen[WithoutExplicitEmpty] | ||
| // Default config has explicitEmptyCollections = ExplicitEmptyCollections(encoding=true, decoding=true) | ||
| // So without annotation, missing "items" should FAIL (same as default config) | ||
| val result = """{"name":"test"}""".fromJson[WithoutExplicitEmpty] | ||
| assertTrue(result == Left(".items(missing)")) |
| test("@jsonExplicitEmptyCollections encodes empty collections") { | ||
| import WithExplicitEmpty._ | ||
| // With annotation (encoding=true, the default), empty list SHOULD be written as "items":[] | ||
| val result = WithExplicitEmpty(List.empty, "test").toJson | ||
| assertTrue(result == """{"items":[],"name":"test"}""") |
a55a9e4 to
5622365
Compare
There was a problem hiding this comment.
Pull request overview
This PR removes the Magnolia dependency for Scala 3 derivation by rewriting zio-json’s codec derivation using native Scala 3 macros, while keeping Scala 2 derivation unchanged. It also updates build/test setup and documentation to reflect the new derivation approach and increased stack requirements for Scala 3 macro expansion.
Changes:
- Replaced Scala 3 codec derivation implementation with native Scala 3 macro-based derivation (products, sums/ADTs, enums, >64 fields).
- Updated build, CI, and JVM options (removed Scala 3 magnolia dependency; increased thread stack size).
- Adjusted/added tests and updated docs/README to reflect the Scala 2 vs Scala 3 derivation split.
Reviewed changes
Copilot reviewed 9 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala | Updates regex assertions to tolerate Scala-version-dependent generated class names. |
| zio-json/shared/src/test/scala-3/zio/json/EncoderVesionSpecificSpec.scala | Adds Scala 3-specific tests for @jsonExplicitEmptyCollections encoding behavior. |
| zio-json/shared/src/test/scala-3/zio/json/DecoderVersionSpecificSpec.scala | Adds Scala 3-specific derivation regression tests (overloaded apply, sealed traits, explicit empty collections, multi-level hierarchies). |
| zio-json/shared/src/test/scala-2.12-2.13/zio/json/internal/FieldEncoderHelperSpec.scala | Adds Scala 2.12/2.13 tests validating FieldEncoder.skip behavior for options/collections/products. |
| zio-json/shared/src/test/scala-2.12-2.13/zio/json/codegen/GeneratorSpec.scala | Adds Scala 2.12/2.13 tests for JSON-to-case-class code generation. |
| zio-json/shared/src/main/scala-3/zio/json/macros.scala | Major rewrite: Scala 3 derivation via native macros + runtime helpers; adds >64 field support and annotation handling. |
| zio-json/jvm/src/test/scala/zio/json/data/geojson/GeoJSON.scala | Updates custom decoders to implement JsonDecoder directly (vs AbstractJsonDecoder). |
| docs/index.mdx | Updates documentation to clarify Magnolia is Scala 2-only; Scala 3 uses native macros. |
| build.sbt | Removes Scala 3 magnolia dependency; adds MiMa excludes for removed classes/methods; adjusts Scala 3 scalacOptions; tweaks JMH classpath. |
| README.md | Same documentation update as docs/index.mdx re: Scala 2 Magnolia vs Scala 3 native macros. |
| .jvmopts | Increases thread stack size to -Xss8m. |
| .github/workflows/ci.yml | Increases CI stack size to -Xss8M for sbt/JDK options. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| val aliases1 = aliases.filter(kv => kv._2 <= splitIndex) | ||
| val aliases2 = aliases.collect { case (k, v) if v > splitIndex => (k, v - splitIndex) } |
| val subtypeInfoArr: Array[(String, Boolean, Boolean, Boolean)] = MacroHelpers.subtypeInfo[A] | ||
| val names: Array[String] = subtypeInfoArr.map(_._1) | ||
| val hasJsonHintArr: Array[Boolean] = subtypeInfoArr.map(_._4) | ||
| val isEnumeration: Boolean = subtypeInfoArr.forall(info => info._2 && !info._3) |
| val isEnumeration: Boolean = subtypeInfoArr.forall(info => info._2 && !info._3) | ||
|
|
| final class NestedWriter(out: Write, indent: Option[Int]) extends Write { | ||
| private[this] var first, second = true | ||
|
|
||
| def write(c: Char): Unit = write(c.toString) // could be optimised | ||
|
|
5622365 to
23ad0ee
Compare
6a694a8 to
aea8c65
Compare
ad161f2 to
e39fbc5
Compare
e39fbc5 to
cd4f942
Compare
| // custom decoders and is not a requirement to use the JsonDecoder[GeoJSON] | ||
| // custom decoder (below) which is necessary to avert a DOS attack. | ||
| implicit lazy val zioJsonJsonDecoder: JsonDecoder[Geometry] = | ||
| new JsonDecoder.AbstractJsonDecoder[Geometry] { |
There was a problem hiding this comment.
Why are you making this change?
e29a56f to
c9b76bd
Compare
|
@hearnadam can you take a look again? |
| val (names1, names2) = names.splitAt(splitIndex) | ||
| val aliases1 = aliases.filter(kv => kv._2 <= splitIndex) | ||
| val aliases2 = aliases.collect { case (k, v) if v > splitIndex => | ||
| (k, v - splitIndex) | ||
| val aliases1 = aliases.filter(kv => kv._2 <= splitIndex) | ||
| val aliases2 = aliases.collect { case (k, v) if v > splitIndex => (k, v - splitIndex) } | ||
| (new StringMatrix(names1, aliases1), new StringMatrix(names2, aliases2)) |
|
|
||
| def write(c: Char): Unit = write(c.toString) // could be optimised |
42867d9 to
a6488aa
Compare
…e time
Replace magnolia with native Scala 3 macros and optimize compile-time performance:
Macro consolidation (16 invocations per DeriveJsonCodec.gen -> ~5):
- Single-pass ProductMeta/SumMeta extraction replaces 9 separate inline helpers
- DeriveJsonCodec.gen shares metadata between encoder and decoder derivation
- Replace inline tuple recursion with macro-generated lists
Generic type and edge case support:
- constructProduct handles generic types via TypeApply on constructor
- fieldAccessors uses Select.unique (public accessor) not Select (private field)
- summonLeafEncoders/Decoders resolve applied types for generic sealed hierarchies
- Default values handle type parameters correctly
- Fix outer accessor issue for locally-defined types
- Fixes for Either[A,B], parametric case classes, type aliases, local enums
- Fix >64-field alias partition off-by-one (splitIndex boundary) + add regression test
- Fix isEnumeration gating to match Scala 2 (check derived CaseObjectDecoder)
- Restore allocation-free NestedWriter state machine from Scala 2
New codecs:
- Add built-in JsonEncoder[Unit] and JsonDecoder[Unit] (encodes as {})
Feature parity with magnolia:
- FieldEncoder with explicitNulls/explicitEmptyCollections
- missingValueDecoder pattern, >64 field split, ADT name validation
- Multi-level sealed hierarchy support via leaf type flattening
- Config propagation through nested derivations
- Null-peek defaults handling matching Scala 2 behavior
Housekeeping:
- Rename EncoderVesionSpecificSpec -> EncoderVersionSpecificSpec (typo fix)
- Remove magnolia1_3 dependency for Scala 3
- Bump -Xss to 8M in .jvmopts and CI
- Add mima binary compat filters
a6488aa to
0ffcdfb
Compare
Summary
Replaces magnolia with native Scala 3 macros for JSON codec derivation, eliminating an external dependency while maintaining full feature parity and binary compatibility.
Changes
Macro rewrite (
macros.scala)FieldEncoderabstraction with properexplicitNullsandexplicitEmptyCollectionsper-field support. Fixed indent handling (indent_for nested field values).explicitEmptyCollections.decodingviamissingValueDecoderpattern — when enabled, missing collection fields produce decode errors instead of silently defaulting to empty. Derived product decoders now extendCollectionJsonDecoder(matching magnolia behavior).StringMatrixsplit support usingLexer.field128/Lexer.enumeration128for types with more than 64 fields+aliases.constructProductto handle parameterless Scala 3 enum cases (singleton refs instead of.apply()calls).Build changes
magnolia1_3dependency for Scala 3 (Scala 2 magnolia unchanged)zio-json-test-datasubproject to break cyclic macro dependencies between test data model files and the macro implementationscala-2.x/scala-3.xdirectories for correct cross-compilation-Xssto8Min.jvmoptsand CI — the native macro inline expansion requires more thread stack than magnolia'sDerivation[T]trait approachCleanup
Test Results
All existing tests pass with zero regressions.