Skip to content

Commit

Permalink
updating dependencies and adding default support for temporals
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrapyre committed May 24, 2024
1 parent f080dc2 commit 238a6ba
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 10 deletions.
3 changes: 2 additions & 1 deletion docs/decoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ implicit val decoder: JsonDecoder[Entity] =
#### 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 precomputed in zio-json to reduce overhead at runtime. If you're using a generator to create a default value (e.g. `UUID.randomUUID()`), zio-json will compute two values from the generator and compare them. If they match, the value will be precomputed. If they do not match, the value will be computed every time a new json string is decoded.
3. The approach in step 2 does not work with time-based values, which can sometimes be different after two evaluations, but sometimes are the same (e.g. `Instant.now()`). To force fresh evaluation of the default parameter every time a new json string is decoded, do something like the following:
3. All default values of a type that extends `java.time.temporal.Temporal` (e.g. `java.time.Instant`) will be computed every time new json is parsed, 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 every time a new json string is decoded, do something like the following:
```scala mdoc
case class DefaultDynamic(
@jsonAlwaysEvaluateDefault
Expand Down
6 changes: 3 additions & 3 deletions project/BuildHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -320,13 +320,13 @@ object BuildHelper {
val jawnAST = "1.5.1"
val jsoniterScala = "2.23.3"
val kindProjector = "0.13.3"
val magnolia2 = "1.1.9+3-96133f8f-SNAPSHOT"
val magnolia3 = "1.3.6+3-f33c4308-SNAPSHOT"
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.10.2"
val scalaCollectionCompat = "2.9.0"
val scalaCollectionCompat = "2.12.0"
val scalaJavaTime = "2.5.0"
val scalaMacros = "2.1.1"
val scalaz = "7.3.7"
Expand Down
13 changes: 11 additions & 2 deletions zio-json/shared/src/main/scala-2.x/zio/json/macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -328,14 +328,23 @@ object DeriveJsonDecoder {
lazy val tcs: Array[JsonDecoder[Any]] =
ctx.parameters.map(_.typeclass).toArray.asInstanceOf[Array[JsonDecoder[Any]]]

//A list of default options for each case class field.
//Each default option is an Option[Either[A,B]] where A is the standard, precomputed default and B is a function to calculate the default each time
//This is necessary to support both defaults that can be precomputed as well as others (e.g. random generators such as UUID.randomUUID()) that need to be freshly calculated each time
lazy val defaults: Array[Option[Either[Any, () => Any]]] = ctx.parameters.map { param =>
val isAlwaysEvaluateAnnotationPresent = param.annotations.collectFirst { case _: jsonAlwaysEvaluateDefault =>
true
}.getOrElse(false)
param.evaluateDefault.flatMap { defaultEvaluator =>
if (isAlwaysEvaluateAnnotationPresent || defaultEvaluator() != defaultEvaluator()) {
val sampleEvaluation = defaultEvaluator()
if (isAlwaysEvaluateAnnotationPresent || sampleEvaluation != defaultEvaluator()) {
Some(Right(defaultEvaluator))
} else None
} else {
sampleEvaluation match {
case _: java.time.temporal.Temporal => Some(Right(defaultEvaluator))
case _ => None
}
}
}.orElse(param.default.map(Left(_)))
}.toArray

Expand Down
13 changes: 11 additions & 2 deletions zio-json/shared/src/main/scala-3/zio/json/macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,21 @@ object DeriveJsonDecoder extends Derivation[JsonDecoder] { self =>
lazy val tcs: Array[JsonDecoder[Any]] =
IArray.genericWrapArray(ctx.params.map(_.typeclass)).toArray.asInstanceOf[Array[JsonDecoder[Any]]]

//A list of default options for each case class field.
//Each default option is an Option[Either[A,B]] where A is the standard, precomputed default and B is a function to calculate the default each time
//This is necessary to support both defaults that can be precomputed as well as others (e.g. random generators such as UUID.randomUUID()) that need to be freshly calculated each time
lazy val defaults: Array[Option[Either[Any, () => Any]]] = ctx.parameters.map { param =>
val isAlwaysEvaluateAnnotationPresent = param.annotations.collectFirst { case _ :jsonAlwaysEvaluateDefault => true }.getOrElse(false)
param.evaluateDefault.flatMap { defaultEvaluator =>
if (isAlwaysEvaluateAnnotationPresent || defaultEvaluator() != defaultEvaluator()) {
val sampleEvaluation = defaultEvaluator()
if (isAlwaysEvaluateAnnotationPresent || sampleEvaluation != defaultEvaluator()) {
Some(Right(defaultEvaluator))
} else None
} else {
sampleEvaluation match {
case _: java.time.temporal.Temporal => Some(Right(defaultEvaluator))
case _ => None
}
}
}.orElse(param.default.map(Left(_)))
}.toArray

Expand Down
29 changes: 27 additions & 2 deletions zio-json/shared/src/test/scala/zio/json/DecoderSpec.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package testzio.json

import testzio.json.DecoderSpec.exampleproducts.DefaultDynamic
import testzio.json.DecoderSpec.exampleproducts.{ DefaultDynamic, DefaultDynamicWithTime }
import zio._
import zio.json._
import zio.json.ast.Json
import zio.test.Assertion._
import zio.test.TestAspect.jvmOnly
import zio.test.TestAspect.{ jvm, jvmOnly, samples }
import zio.test._

import java.time.{ Duration, Instant, OffsetDateTime, ZonedDateTime }
Expand Down Expand Up @@ -161,6 +161,23 @@ object DecoderSpec extends ZIOSpecDefault {
assertTrue(dynamics._1.randomNumberWithoutAnnotation != dynamics._2.randomNumberWithoutAnnotation)
} yield finalRes
},
test("dynamic default value with time") {
val res1 = """{}""".stripMargin.fromJson[DefaultDynamicWithTime]
val res2 = """{}""".stripMargin.fromJson[DefaultDynamicWithTime]
val res = ZIO.fromEither {
for {
json1 <- res1
json2 <- res2
} yield (json1, json2)
}

for {
dynamics <- res
finalRes <-
assert(res1)(isRight) && assert(res2)(isRight) &&
assertTrue(dynamics._1.instant != dynamics._2.instant)
} yield finalRes
} @@ jvm(samples(100)) @@ jvmOnly,
test("sum encoding") {
import examplesum._

Expand Down Expand Up @@ -555,6 +572,14 @@ object DecoderSpec extends ZIOSpecDefault {
implicit val decoder: JsonDecoder[DefaultDynamic] = DeriveJsonDecoder.gen[DefaultDynamic]
}

case class DefaultDynamicWithTime(
instant: Instant = Instant.now()
)

object DefaultDynamicWithTime {
implicit val decoder: JsonDecoder[DefaultDynamicWithTime] = DeriveJsonDecoder.gen[DefaultDynamicWithTime]
}

case class Inner(str: String)

object Inner {
Expand Down

0 comments on commit 238a6ba

Please sign in to comment.