Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: default case class values memoized #1119

Open
wants to merge 10 commits into
base: series/2.x
Choose a base branch
from
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
fail-fast: false
matrix:
java: ['11', '17']
scala: ['2.13.13']
scala: ['2.13.14']
steps:
- name: Checkout current branch
uses: actions/[email protected]
Expand Down Expand Up @@ -79,7 +79,7 @@ jobs:
fail-fast: false
matrix:
java: ['11', '17']
scala: ['2.12.19', '2.13.13', '3.3.3']
scala: ['2.12.19', '2.13.14', '3.3.3']
platform: ['JVM', 'JS', 'Native']
steps:
- name: Checkout current branch
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
java-version: 17
check-latest: true
- name: Check artifacts build process
run: sbt +publishLocal
run: sbt +publishLocal
- name: Check website build process
run: sbt docs/clean; sbt docs/buildWebsite
publish-docs:
Expand Down
98 changes: 47 additions & 51 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ addCommandAlias(
"zioJsonNative/test; zioJsonInteropScalaz7xNative/test"
)

val zioVersion = "2.0.21"

lazy val zioJsonRoot = project
.in(file("."))
.settings(
Expand All @@ -82,8 +80,6 @@ lazy val zioJsonRoot = project
zioJsonGolden
)

val circeVersion = "0.14.3"

lazy val zioJson = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.in(file("zio-json"))
.settings(stdSettings("zio-json"))
Expand All @@ -92,7 +88,6 @@ lazy val zioJson = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.enablePlugins(NeoJmhPlugin)
.settings(
scalacOptions -= "-Xfatal-warnings", // not quite ready.

// as per @fommil, optimization slows things down.
scalacOptions -= "-opt:l:inline",
scalacOptions -= "-opt-inline-from:zio.internal.**",
Expand All @@ -103,30 +98,30 @@ lazy val zioJson = crossProject(JSPlatform, JVMPlatform, NativePlatform)
Vector.empty
},
libraryDependencies ++= Seq(
"dev.zio" %%% "zio" % zioVersion,
"dev.zio" %%% "zio-streams" % zioVersion,
"org.scala-lang.modules" %%% "scala-collection-compat" % "2.9.0",
"dev.zio" %%% "zio-test" % zioVersion % "test",
"dev.zio" %%% "zio-test-sbt" % zioVersion % "test",
"io.circe" %%% "circe-core" % circeVersion % "test",
"io.circe" %%% "circe-generic" % circeVersion % "test",
"io.circe" %%% "circe-parser" % circeVersion % "test"
"dev.zio" %%% "zio" % Dependencies.zio,
"dev.zio" %%% "zio-streams" % Dependencies.zio,
"org.scala-lang.modules" %%% "scala-collection-compat" % Dependencies.scalaCollectionCompat,
"dev.zio" %%% "zio-test" % Dependencies.zio % "test",
"dev.zio" %%% "zio-test-sbt" % Dependencies.zio % "test",
"io.circe" %%% "circe-core" % Dependencies.circe % "test",
"io.circe" %%% "circe-generic" % Dependencies.circe % "test",
"io.circe" %%% "circe-parser" % Dependencies.circe % "test"
),
// scala version specific dependencies
libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, _)) =>
Vector(
"com.softwaremill.magnolia1_3" %%% "magnolia" % "1.3.0"
"com.softwaremill.magnolia1_3" %%% "magnolia" % Dependencies.magnolia3
)

case _ =>
Vector(
"org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided,
"com.softwaremill.magnolia1_2" %%% "magnolia" % "1.1.8",
"io.circe" %%% "circe-generic-extras" % circeVersion % "test",
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.23.3" % "test",
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.23.3" % "test"
"org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided,
"com.softwaremill.magnolia1_2" %%% "magnolia" % Dependencies.magnolia2,
"io.circe" %%% "circe-generic-extras" % Dependencies.circeGenericExtras % "test",
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % Dependencies.jsoniterScala % "test",
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % Dependencies.jsoniterScala % "test"
)
}
},
Expand Down Expand Up @@ -218,30 +213,30 @@ lazy val zioJson = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.settings(testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"))
.jsSettings(
libraryDependencies ++= Seq(
"io.github.cquiroz" %%% "scala-java-time" % "2.5.0",
"io.github.cquiroz" %%% "scala-java-time-tzdb" % "2.6.0"
"io.github.cquiroz" %%% "scala-java-time" % Dependencies.scalaJavaTime,
"io.github.cquiroz" %%% "scala-java-time-tzdb" % Dependencies.scalaJavaTime
)
)
.jvmSettings(
libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, _)) =>
Vector(
"org.typelevel" %% "jawn-ast" % "1.6.0" % "test"
"org.typelevel" %% "jawn-ast" % Dependencies.jawnAST % "test"
)

case Some((2, n)) =>
if (n >= 13) {
Seq(
"com.particeep" %% "play-json-extensions" % "0.43.1" % "test",
"com.typesafe.play" %%% "play-json" % "2.9.4" % "test",
"org.typelevel" %% "jawn-ast" % "1.6.0" % "test"
"com.particeep" %% "play-json-extensions" % Dependencies.playJsonExtensions % "test",
"com.typesafe.play" %%% "play-json" % Dependencies.playJson % "test",
"org.typelevel" %% "jawn-ast" % Dependencies.jawnAST % "test"
)
} else {
Seq(
"ai.x" %% "play-json-extensions" % "0.42.0" % "test",
"com.typesafe.play" %%% "play-json" % "2.9.4" % "test",
"org.typelevel" %% "jawn-ast" % "1.6.0" % "test"
"ai.x" %% "play-json-extensions" % Dependencies.playJsonExtensions2_12 % "test",
"com.typesafe.play" %%% "play-json" % Dependencies.playJson % "test",
"org.typelevel" %% "jawn-ast" % Dependencies.jawnAST % "test"
)
}

Expand All @@ -253,7 +248,7 @@ lazy val zioJson = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.nativeSettings(Test / fork := false)
.nativeSettings(
libraryDependencies ++= Seq(
"io.github.cquiroz" %%% "scala-java-time" % "2.5.0"
"io.github.cquiroz" %%% "scala-java-time" % Dependencies.scalaJavaTime
)
)
.enablePlugins(BuildInfoPlugin)
Expand All @@ -273,10 +268,10 @@ lazy val zioJsonGolden = project
.settings(buildInfoSettings("zio.json.golden"))
.settings(
libraryDependencies ++= Seq(
"dev.zio" %% "zio" % zioVersion,
"dev.zio" %% "zio-test" % zioVersion,
"dev.zio" %% "zio-test-sbt" % zioVersion,
"dev.zio" %% "zio-test-magnolia" % zioVersion
"dev.zio" %% "zio" % Dependencies.zio,
"dev.zio" %% "zio-test" % Dependencies.zio,
"dev.zio" %% "zio-test-sbt" % Dependencies.zio,
"dev.zio" %% "zio-test-magnolia" % Dependencies.zio
),
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework")
)
Expand All @@ -289,10 +284,10 @@ lazy val zioJsonYaml = project
.settings(buildInfoSettings("zio.json.yaml"))
.settings(
libraryDependencies ++= Seq(
"org.yaml" % "snakeyaml" % "2.2",
"dev.zio" %% "zio" % zioVersion,
"dev.zio" %% "zio-test" % zioVersion % "test",
"dev.zio" %% "zio-test-sbt" % zioVersion % "test"
"org.yaml" % "snakeyaml" % Dependencies.snakeYaml,
"dev.zio" %% "zio" % Dependencies.zio,
"dev.zio" %% "zio-test" % Dependencies.zio % "test",
"dev.zio" %% "zio-test-sbt" % Dependencies.zio % "test"
),
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework")
)
Expand All @@ -310,8 +305,8 @@ lazy val zioJsonMacros = crossProject(JSPlatform, JVMPlatform, NativePlatform)
scalacOptions -= "-Xfatal-warnings", // not quite ready.
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided,
"dev.zio" %%% "zio-test" % zioVersion % "test",
"dev.zio" %%% "zio-test-sbt" % zioVersion % "test"
"dev.zio" %%% "zio-test" % Dependencies.zio % "test",
"dev.zio" %%% "zio-test-sbt" % Dependencies.zio % "test"
),
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework")
)
Expand All @@ -330,12 +325,12 @@ lazy val zioJsonInteropHttp4s = project
.settings(
crossScalaVersions -= ScalaDotty,
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-dsl" % "0.23.26",
"dev.zio" %% "zio" % zioVersion,
"org.typelevel" %% "cats-effect" % "3.4.9",
"dev.zio" %% "zio-interop-cats" % "23.0.03" % "test",
"dev.zio" %% "zio-test" % zioVersion % "test",
"dev.zio" %% "zio-test-sbt" % zioVersion % "test"
"org.http4s" %% "http4s-dsl" % Dependencies.http4s,
"dev.zio" %% "zio" % Dependencies.zio,
"org.typelevel" %% "cats-effect" % Dependencies.catsEffect,
"dev.zio" %% "zio-interop-cats" % Dependencies.zioInteropCats % "test",
"dev.zio" %% "zio-test" % Dependencies.zio % "test",
"dev.zio" %% "zio-test-sbt" % Dependencies.zio % "test"
),
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework")
)
Expand All @@ -349,9 +344,9 @@ lazy val zioJsonInteropRefined = crossProject(JSPlatform, JVMPlatform, NativePla
.settings(buildInfoSettings("zio.json.interop.refined"))
.settings(
libraryDependencies ++= Seq(
"eu.timepit" %%% "refined" % "0.10.2",
"dev.zio" %%% "zio-test" % zioVersion % "test",
"dev.zio" %%% "zio-test-sbt" % zioVersion % "test"
"eu.timepit" %%% "refined" % Dependencies.refined,
"dev.zio" %%% "zio-test" % Dependencies.zio % "test",
"dev.zio" %%% "zio-test-sbt" % Dependencies.zio % "test"
),
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework")
)
Expand All @@ -362,12 +357,13 @@ lazy val zioJsonInteropScalaz7x = crossProject(JSPlatform, JVMPlatform, NativePl
.dependsOn(zioJson)
.settings(stdSettings("zio-json-interop-scalaz7x"))
.settings(buildInfoSettings("zio.json.interop.scalaz7x"))
.settings(nativeSettings)
.settings(
crossScalaVersions -= ScalaDotty,
libraryDependencies ++= Seq(
"org.scalaz" %%% "scalaz-core" % "7.3.7",
"dev.zio" %%% "zio-test" % zioVersion % "test",
"dev.zio" %%% "zio-test-sbt" % zioVersion % "test"
"org.scalaz" %%% "scalaz-core" % Dependencies.scalaz,
"dev.zio" %%% "zio-test" % Dependencies.zio % "test",
"dev.zio" %%% "zio-test-sbt" % Dependencies.zio % "test"
),
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework")
)
Expand Down
17 changes: 16 additions & 1 deletion docs/decoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,22 @@ implicit val decoder: JsonDecoder[Entity] =
"""{ "id": 42, "related": null }""".fromJson[Entity]
```

_Note: If you’re using Scala 3 and your case class is defining default parameters, `-Yretain-trees` needs to be added to `scalacOptions`._
#### Notes:
1. If you’re using Scala 3 and your case class is defining default parameters, `-Yretain-trees` needs to be added to `scalacOptions`.
2. Default values are generally computed at build time in zio-json to reduce overhead during json parsing. If you're using a generator to create a default value (e.g. `UUID.randomUUID()`), zio-json will compute two values from the generator at build time and compare them. If they match, the final value will be computed at build time. If they do not match, the value will be computed every time a new json string is decoded.
3. All default values of a type that extends `java.time.temporal.Temporal` (e.g. `java.time.Instant`) will be computed during json parsing, since these values are overwhelmingly used as fresh timestamps.
4. However, there may be other, non-`java.time.temporal.Temporal` (e.g. timestamps generated by 3rd party libs like `org.joda.time`) values that are sometimes different after two evaluations, but sometimes are the same. To force fresh evaluation of the default parameter during json parsing, add the following annotation like so:
```scala mdoc
import java.time.Instant

case class DefaultDynamic(
//As noted above, Instant.now() will never be computed at build time. This is just an example of how to force computation during json parsing
@jsonAlwaysEvaluateDefault
instant: Instant = Instant.now()
)
```



## ADTs

Expand Down
69 changes: 47 additions & 22 deletions project/BuildHelper.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import explicitdeps.ExplicitDepsPlugin.autoImport._
import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport._
import sbt.Keys._
import sbt._
import sbtbuildinfo.BuildInfoKeys._
import sbtbuildinfo._
import sbtcrossproject.CrossPlugin.autoImport._
import explicitdeps.ExplicitDepsPlugin.autoImport.*
import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport.*
import sbt.Keys.*
import sbt.*
import sbtbuildinfo.BuildInfoKeys.*
import sbtbuildinfo.*
import sbtcrossproject.CrossPlugin.autoImport.*

import scala.scalanative.sbtplugin.ScalaNativePlugin.autoImport.nativeConfig

object BuildHelper {
private val versions: Map[String, String] = {
Expand All @@ -22,8 +24,6 @@ object BuildHelper {
val Scala213: String = versions("2.13")
val ScalaDotty: String = "3.3.3"

val SilencerVersion = "1.7.16"

private val stdOptions = Seq(
"-deprecation",
"-encoding",
Expand Down Expand Up @@ -95,7 +95,7 @@ object BuildHelper {
)

val scalaReflectSettings = Seq(
libraryDependencies ++= Seq("dev.zio" %%% "izumi-reflect" % "1.0.0-M10")
libraryDependencies ++= Seq("dev.zio" %%% "izumi-reflect" % Dependencies.zioReflect)
)

// Keep this consistent with the version in .core-tests/shared/src/test/scala/REPLSpec.scala
Expand Down Expand Up @@ -208,7 +208,7 @@ object BuildHelper {
baseDirectory.value
)
}
)
) ++ nativeSettings

def stdSettings(prjName: String) = Seq(
name := s"$prjName",
Expand All @@ -218,18 +218,18 @@ object BuildHelper {
libraryDependencies ++= {
if (scalaVersion.value == ScalaDotty)
Seq(
"com.github.ghik" % s"silencer-lib_$Scala213" % SilencerVersion % Provided
"com.github.ghik" % s"silencer-lib_$Scala213" % Dependencies.silencer % Provided
)
else
Seq(
"com.github.ghik" % "silencer-lib" % SilencerVersion % Provided cross CrossVersion.full,
compilerPlugin("com.github.ghik" % "silencer-plugin" % SilencerVersion cross CrossVersion.full),
compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.3" cross CrossVersion.full)
"com.github.ghik" % "silencer-lib" % Dependencies.silencer % Provided cross CrossVersion.full,
compilerPlugin("com.github.ghik" % "silencer-plugin" % Dependencies.silencer cross CrossVersion.full),
compilerPlugin("org.typelevel" %% "kind-projector" % Dependencies.kindProjector cross CrossVersion.full)
)
},
semanticdbEnabled := scalaVersion.value != ScalaDotty, // enable SemanticDB
semanticdbOptions += "-P:semanticdb:synthetics:on",
semanticdbVersion := "4.9.2",
semanticdbVersion := Dependencies.semanticDB,
Test / parallelExecution := true,
incOptions ~= (_.withLogRecompileOnMacro(false)),
autoAPIMappings := true,
Expand All @@ -246,7 +246,7 @@ object BuildHelper {
libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, x)) if x <= 12 =>
Seq(compilerPlugin(("org.scalamacros" % "paradise" % "2.1.1").cross(CrossVersion.full)))
Seq(compilerPlugin(("org.scalamacros" % "paradise" % Dependencies.scalaMacros).cross(CrossVersion.full)))
case _ => Seq.empty
}
}
Expand All @@ -265,14 +265,13 @@ object BuildHelper {
)

def jsSettings = Seq(
libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % "2.2.2",
libraryDependencies += "io.github.cquiroz" %%% "scala-java-time-tzdb" % "2.2.2"
libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % Dependencies.scalaJavaTime,
libraryDependencies += "io.github.cquiroz" %%% "scala-java-time-tzdb" % Dependencies.scalaJavaTime
)

//The initial upgrade to the Scala Native 0.5.x series does not support native multithreading with ZIO. See https://github.com/zio/zio/pull/8840.
def nativeSettings = Seq(
Test / skip := true,
doc / skip := true,
Compile / doc / sources := Seq.empty
nativeConfig ~= { _.withMultithreading(false) }
)

val scalaReflectTestSettings: List[Setting[_]] = List(
Expand Down Expand Up @@ -314,4 +313,30 @@ object BuildHelper {
implicit class ModuleHelper(p: Project) {
def module: Project = p.in(file(p.id)).settings(stdSettings(p.id))
}

object Dependencies {
val catsEffect = "3.4.9"
val circe = "0.14.9"
val circeGenericExtras = "0.14.4"
val http4s = "0.23.26"
val jawnAST = "1.6.0"
val jsoniterScala = "2.30.7"
val kindProjector = "0.13.3"
val magnolia2 = "1.1.10"
val magnolia3 = "1.3.7"
val playJson = "2.9.4"
val playJsonExtensions = "0.43.1"
val playJsonExtensions2_12 = "0.42.0"
val refined = "0.11.2"
val scalaCollectionCompat = "2.12.0"
val scalaJavaTime = "2.6.0"
val scalaMacros = "2.1.1"
val scalaz = "7.2.36"
val semanticDB = "4.9.9"
val silencer = "1.7.17"
val snakeYaml = "2.2"
val zio = "2.1.7"
val zioInteropCats = "23.1.0.2"
val zioReflect = "1.0.0-M10"
}
}
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.10.0")
addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.1")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.1")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0")
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.17")
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.1")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0")
addSbtPlugin("pl.project13.scala" % "sbt-jcstress" % "0.2.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11")
Expand Down
Loading
Loading