Skip to content

Remove magnolia dependency for Scala 3, use native macros - #1565

Merged
987Nabil merged 1 commit into
zio:series/2.xfrom
987Nabil:remove-magnolia
Sep 1, 2026
Merged

Remove magnolia dependency for Scala 3, use native macros#1565
987Nabil merged 1 commit into
zio:series/2.xfrom
987Nabil:remove-magnolia

Conversation

@987Nabil

Copy link
Copy Markdown
Contributor

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)

  • Encoder: Rewrote product encoder to use FieldEncoder abstraction with proper explicitNulls and explicitEmptyCollections per-field support. Fixed indent handling (indent_ for nested field values).
  • Decoder: Wired explicitEmptyCollections.decoding via missingValueDecoder pattern — when enabled, missing collection fields produce decode errors instead of silently defaulting to empty. Derived product decoders now extend CollectionJsonDecoder (matching magnolia behavior).
  • >64 fields: Added StringMatrix split support using Lexer.field128/Lexer.enumeration128 for types with more than 64 fields+aliases.
  • ADT validation: Added subtype name distinctness check with descriptive collision error messages.
  • Enum support: Fixed constructProduct to handle parameterless Scala 3 enum cases (singleton refs instead of .apply() calls).

Build changes

  • Removed magnolia1_3 dependency for Scala 3 (Scala 2 magnolia unchanged)
  • Created zio-json-test-data subproject to break cyclic macro dependencies between test data model files and the macro implementation
  • Split platform-specific test files into scala-2.x/scala-3.x directories for correct cross-compilation
  • Bumped -Xss to 8M in .jvmopts and CI — the native macro inline expansion requires more thread stack than magnolia's Derivation[T] trait approach

Cleanup

  • Removed reference/backup files from earlier migration attempts

Test Results

Platform Passed Failed Ignored
Scala 3 JVM 553 0 2
Scala 3 JS 106 0 0
Scala 3 Native 129 0 3
Scala 2 JVM 779 0 3

All existing tests pass with zero regressions.

@987Nabil
987Nabil requested a review from a team as a code owner March 14, 2026 21:52
@987Nabil
987Nabil requested a review from Copilot March 14, 2026 22:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.jsontest and split Scala 2 vs Scala 3 test sources to support cross-compilation.
  • Updated build and CI settings (removed Magnolia for Scala 3, added zioJsonTestData module, 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.

Comment on lines +599 to 613
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
}
Comment on lines +994 to +995
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]

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.jsontest and a new zio-json-test-data subproject).
  • 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.

Comment on lines +1014 to +1030
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)
}
@987Nabil
987Nabil force-pushed the remove-magnolia branch 2 times, most recently from 84bf068 to 1b93d01 Compare March 15, 2026 15:58
@987Nabil
987Nabil requested a review from Copilot March 15, 2026 21:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.scala using inline + quoted macros, including >64-field support and additional validation.
  • Removed the Scala 3 magnolia1_3 dependency 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.

Comment on lines +556 to +557
val opt = JsonDecoder.option(tcs(field)).unsafeDecode(trace_, in)
ps(field) = opt.getOrElse(defaults(field).get.apply())
Comment on lines +1233 to +1256
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)
@987Nabil
987Nabil force-pushed the remove-magnolia branch 2 times, most recently from 6cf827c to ee24653 Compare March 16, 2026 09:55
@987Nabil
987Nabil requested a review from Copilot March 16, 2026 14:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +1020 to +1024
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)
}
Comment on lines 1032 to 1036
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 `[]`.
Comment on lines +195 to +200
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)"))
Comment on lines +92 to +96
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"}""")
@987Nabil
987Nabil force-pushed the remove-magnolia branch 4 times, most recently from a55a9e4 to 5622365 Compare March 18, 2026 14:54
@987Nabil
987Nabil requested a review from Copilot March 19, 2026 09:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +578 to +579
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)
Comment on lines +1286 to +1287
val isEnumeration: Boolean = subtypeInfoArr.forall(info => info._2 && !info._3)

Comment on lines +1306 to +1310
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

@987Nabil
987Nabil force-pushed the remove-magnolia branch 2 times, most recently from 6a694a8 to aea8c65 Compare April 3, 2026 11:10
@987Nabil
987Nabil force-pushed the remove-magnolia branch 2 times, most recently from ad161f2 to e39fbc5 Compare April 25, 2026 13:09
// 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] {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you making this change?

@987Nabil
987Nabil force-pushed the remove-magnolia branch 5 times, most recently from e29a56f to c9b76bd Compare June 24, 2026 10:10
@987Nabil

Copy link
Copy Markdown
Contributor Author

@hearnadam can you take a look again?

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 16 changed files in this pull request and generated 2 comments.

Comment on lines 839 to +842
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))
Comment on lines +1565 to +1566

def write(c: Char): Unit = write(c.toString) // could be optimised
…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
@987Nabil
987Nabil merged commit 9e16384 into zio:series/2.x Sep 1, 2026
22 checks passed
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

Successfully merging this pull request may close these issues.

3 participants