Skip to content

Commit aea8c65

Browse files
committed
refactor(macros): remove magnolia for Scala 3 + optimize macro compile 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 - summonLeafEncoders/Decoders resolve applied types for generic sealed hierarchies - Default values handle type parameters correctly - Fix outer accessor issue for locally-defined types (no mirror capture) - Fixes for Either[A,B], parametric case classes, type aliases, local enums 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 Build changes: - Remove magnolia1_3 dependency for Scala 3 - Bump -Xss to 8M in .jvmopts and CI - Add mima binary compat filters
1 parent 632e6df commit aea8c65

13 files changed

Lines changed: 1650 additions & 795 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
name: CI
22

33
env:
4-
JDK_JAVA_OPTIONS: -XX:+PrintCommandLineFlags -Xms6G -Xmx6G -Xss4M -XX:+UseG1GC -XX:ReservedCodeCacheSize=512M -XX:NonProfiledCodeHeapSize=256M # JDK_JAVA_OPTIONS is _the_ env. variable to use for modern Java
5-
SBT_OPTS: -XX:+PrintCommandLineFlags -Xms6G -Xmx6G -Xss4M -XX:+UseG1GC -XX:ReservedCodeCacheSize=512M -XX:NonProfiledCodeHeapSize=256M # Needed for sbt
4+
JDK_JAVA_OPTIONS: -XX:+PrintCommandLineFlags -Xms6G -Xmx6G -Xss8M -XX:+UseG1GC -XX:ReservedCodeCacheSize=512M -XX:NonProfiledCodeHeapSize=256M # JDK_JAVA_OPTIONS is _the_ env. variable to use for modern Java
5+
SBT_OPTS: -XX:+PrintCommandLineFlags -Xms6G -Xmx6G -Xss8M -XX:+UseG1GC -XX:ReservedCodeCacheSize=512M -XX:NonProfiledCodeHeapSize=256M # Needed for sbt
66

77
on:
88
pull_request:

.jvmopts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-Xmx6g
2-
-Xss2m
2+
-Xss8m
33
-XX:+UseG1GC
44
-XX:InitialCodeCacheSize=512m
55
-XX:ReservedCodeCacheSize=512m

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ High **performance** is achieved by decoding JSON directly from the input source
171171
172172
Best in class **security** is achieved with an aggressive *early exit* strategy that avoids costly stack traces, even when parsing malformed numbers. Malicious (and badly formed) payloads are rejected before finishing reading.
173173
174-
**Fast compilation** and **future-proofing** is possible thanks to [Magnolia](https://propensive.com/opensource/magnolia/) which allows us to generate boilerplate in a way that will survive the exodus to Scala 3. `zio-json` is internally implemented using a [`java.io.Reader`](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/io/Reader.html) / [`java.io.Writer`](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/io/Writer.html)-like interface, which is making a comeback to center stage in Loom.
174+
**Fast compilation** and **future-proofing**: For Scala 2, derivation uses [Magnolia](https://propensive.com/opensource/magnolia/). For Scala 3, derivation uses native Scala 3 macros. `zio-json` is internally implemented using a [`java.io.Reader`](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/io/Reader.html) / [`java.io.Writer`](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/io/Writer.html)-like interface, which is making a comeback to center stage in Loom.
175175
176176
**Simplicity** is achieved by using well-known software patterns and avoiding bloat. The only requirement to use this library is to know about Scala's encoding of typeclasses, described in [Functional Programming for Mortals](https://leanpub.com/fpmortals/read#leanpub-auto-functionality).
177177

build.sbt

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import BuildHelper.*
2-
import com.typesafe.tools.mima.core.Problem
2+
import com.typesafe.tools.mima.core.*
33
import com.typesafe.tools.mima.core.ProblemFilters.exclude
44
import com.typesafe.tools.mima.plugin.MimaKeys.mimaPreviousArtifacts
55
import explicitdeps.ExplicitDepsPlugin.autoImport.moduleFilterRemoveValue
@@ -107,7 +107,7 @@ lazy val zioJson = crossProject(JSPlatform, JVMPlatform, NativePlatform)
107107
// as per @fommil, optimization slows things down.
108108
scalacOptions -= "-opt:l:inline",
109109
scalacOptions -= "-opt-inline-from:zio.internal.**",
110-
Test / scalacOptions ++= {
110+
scalacOptions ++= {
111111
if (scalaVersion.value == Scala3)
112112
Vector("-Yretain-trees", "-Xmax-inlines:128")
113113
else
@@ -130,9 +130,7 @@ lazy val zioJson = crossProject(JSPlatform, JVMPlatform, NativePlatform)
130130
libraryDependencies ++= {
131131
CrossVersion.partialVersion(scalaVersion.value) match {
132132
case Some((3, _)) =>
133-
Seq(
134-
"com.softwaremill.magnolia1_3" %%% "magnolia" % "1.3.18"
135-
)
133+
Seq.empty
136134
case _ =>
137135
Seq(
138136
"org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided,
@@ -235,9 +233,33 @@ lazy val zioJson = crossProject(JSPlatform, JVMPlatform, NativePlatform)
235233
inConfig(Jmh)(org.scalafmt.sbt.ScalafmtPlugin.scalafmtConfigSettings(Jmh)),
236234
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"),
237235
mimaBinaryIssueFilters ++= Seq(
238-
exclude[Problem]("zio.json.CaseObjectDecoder.*") // FIXME: false negative reported by mima
236+
exclude[Problem]("zio.json.CaseObjectDecoder.*"),
237+
exclude[MissingClassProblem]("zio.json.CaseObjectDecoder"),
238+
exclude[DirectMissingMethodProblem]("zio.json.DeriveJsonDecoder.join"),
239+
exclude[DirectMissingMethodProblem]("zio.json.DeriveJsonDecoder.split"),
240+
exclude[DirectMissingMethodProblem]("zio.json.DeriveJsonDecoder.subtypes$default$2"),
241+
exclude[DirectMissingMethodProblem]("zio.json.DeriveJsonDecoder.getParams$default$4"),
242+
exclude[DirectMissingMethodProblem]("zio.json.DeriveJsonDecoder.getParams_$default$5"),
243+
exclude[DirectMissingMethodProblem]("zio.json.DeriveJsonDecoder.getParams__$default$6"),
244+
exclude[MissingTypesProblem]("zio.json.DeriveJsonDecoder$"),
245+
exclude[MissingClassProblem]("zio.json.DeriveJsonDecoder$ArraySeq"),
246+
exclude[DirectMissingMethodProblem]("zio.json.DeriveJsonEncoder.join"),
247+
exclude[DirectMissingMethodProblem]("zio.json.DeriveJsonEncoder.split"),
248+
exclude[DirectMissingMethodProblem]("zio.json.DeriveJsonEncoder.subtypes$default$2"),
249+
exclude[DirectMissingMethodProblem]("zio.json.DeriveJsonEncoder.getParams$default$4"),
250+
exclude[DirectMissingMethodProblem]("zio.json.DeriveJsonEncoder.getParams_$default$5"),
251+
exclude[DirectMissingMethodProblem]("zio.json.DeriveJsonEncoder.getParams__$default$6"),
252+
exclude[MissingTypesProblem]("zio.json.DeriveJsonEncoder$"),
253+
exclude[MissingClassProblem]("zio.json.JsonDecoderDerivation"),
254+
exclude[MissingClassProblem]("zio.json.JsonDecoderDerivation$ArraySeq"),
255+
exclude[MissingClassProblem]("zio.json.JsonEncoderDerivation"),
256+
exclude[MissingClassProblem]("zio.json.macros$package"),
257+
exclude[MissingClassProblem]("zio.json.macros$package$")
239258
)
240259
)
260+
.jvmSettings(
261+
Jmh / unmanagedClasspath ++= (Test / fullClasspath).value
262+
)
241263
.jsSettings(
242264
mimaBinaryIssueFilters ++= Seq(
243265
exclude[Problem]("zio.JsonPackagePlatformSpecific.*"),

docs/index.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ In order to use this library, we need to add the following line in our `build.sb
3131
libraryDependencies += "dev.zio" %% "zio-json" % "@VERSION@"
3232
```
3333

34-
For cross-platform projects with Scala.js and Scala Native need to replace `%%` operator by `%%%`,
35-
and optionally when using `java.time.ZoneId` and `java.time.ZonedDateTime` types need to add
34+
For cross-platform projects with Scala.js and Scala Native need to replace `%%` operator by `%%%`,
35+
and optionally when using `java.time.ZoneId` and `java.time.ZonedDateTime` types need to add
3636
the dependency on the latest version of Timezone DB:
3737

3838
```scala
@@ -269,7 +269,7 @@ Extreme **performance** is achieved by decoding JSON directly from the input sou
269269

270270
Best in class **security** is achieved with an aggressive *early exit* strategy that avoids costly stack traces, even when parsing malformed numbers. Malicious (and badly formed) payloads are rejected before finishing reading.
271271

272-
**Fast compilation** and **future-proofing** is possible thanks to [Magnolia](https://propensive.com/opensource/magnolia/) which allows us to generate boilerplate in a way that will survive the exodus to Scala 3. `zio-json` is internally implemented using a [`java.io.Reader`](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/io/Reader.html) / [`java.io.Writer`](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/io/Writer.html)-like interface, which is making a comeback to center stage in Loom.
272+
**Fast compilation** and **future-proofing**: For Scala 2, derivation uses [Magnolia](https://propensive.com/opensource/magnolia/). For Scala 3, derivation uses native Scala 3 macros. `zio-json` is internally implemented using a [`java.io.Reader`](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/io/Reader.html) / [`java.io.Writer`](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/io/Writer.html)-like interface, which is making a comeback to center stage in Loom.
273273

274274
**Simplicity** is achieved by using well-known software patterns and avoiding bloat. The only requirement to use this library is to know about Scala's encoding of typeclasses, described in [Functional Programming for Mortals](https://leanpub.com/fpmortals/read#leanpub-auto-functionality).
275275

zio-json/jvm/src/test/scala/zio/json/data/geojson/GeoJSON.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ package handrolled {
120120
// custom decoders and is not a requirement to use the JsonDecoder[GeoJSON]
121121
// custom decoder (below) which is necessary to avert a DOS attack.
122122
implicit lazy val zioJsonJsonDecoder: JsonDecoder[Geometry] =
123-
new JsonDecoder.AbstractJsonDecoder[Geometry] {
123+
new JsonDecoder[Geometry] {
124124
import zio.json._
125125
import JsonDecoder.JsonError
126126
import internal._
@@ -270,7 +270,7 @@ package handrolled {
270270
// potentially complex and even skipping over it is expensive... it's a bit
271271
// of a corner case.
272272
implicit lazy val zioJsonJsonDecoder: JsonDecoder[GeoJSON] =
273-
new JsonDecoder.AbstractJsonDecoder[GeoJSON] {
273+
new JsonDecoder[GeoJSON] {
274274
import zio.json._
275275
import JsonDecoder.JsonError
276276
import internal._

0 commit comments

Comments
 (0)