diff --git a/build.sbt b/build.sbt index aa577ae1..d1679832 100644 --- a/build.sbt +++ b/build.sbt @@ -79,6 +79,8 @@ lazy val zioJsonRoot = project zioJsonInteropScalaz7x.js, zioJsonInteropScalaz7x.jvm, zioJsonInteropScalaz7x.native, + zioJsonInteropNewtype.js, + zioJsonInteropNewtype.jvm, zioJsonGolden ) @@ -357,6 +359,22 @@ lazy val zioJsonInteropRefined = crossProject(JSPlatform, JVMPlatform, NativePla ) .enablePlugins(BuildInfoPlugin) +lazy val zioJsonInteropNewtype = crossProject(JSPlatform, JVMPlatform) + .in(file("zio-json-interop-newtype")) + .dependsOn(zioJson) + .settings(stdSettings("zio-json-interop-newtype")) + .settings(buildInfoSettings("zio.json.interop.newtype")) + .settings(macroExpansionSettings) + .settings( + libraryDependencies ++= Seq( + "io.estatico" %%% "newtype" % "0.4.4", + "dev.zio" %%% "zio-test" % zioVersion % "test", + "dev.zio" %%% "zio-test-sbt" % zioVersion % "test" + ), + testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") + ) + .enablePlugins(BuildInfoPlugin) + lazy val zioJsonInteropScalaz7x = crossProject(JSPlatform, JVMPlatform, NativePlatform) .in(file("zio-json-interop-scalaz7x")) .dependsOn(zioJson) @@ -398,6 +416,7 @@ lazy val docs = project zioJsonInteropHttp4s, zioJsonInteropRefined.jvm, zioJsonInteropScalaz7x.jvm, + zioJsonInteropNewtype.jvm, zioJsonGolden ), readmeAcknowledgement := diff --git a/zio-json-interop-newtype/shared/src/main/scala/zio/json/interop/newtype/package.scala b/zio-json-interop-newtype/shared/src/main/scala/zio/json/interop/newtype/package.scala new file mode 100644 index 00000000..81cf6537 --- /dev/null +++ b/zio-json-interop-newtype/shared/src/main/scala/zio/json/interop/newtype/package.scala @@ -0,0 +1,19 @@ +package zio.json.interop + +import io.estatico.newtype.Coercible +import io.estatico.newtype.ops._ +import zio.json.{ JsonDecoder, JsonEncoder, JsonFieldDecoder, JsonFieldEncoder } + +package object newtype { + implicit def coercibleDecoder[A: Coercible[B, *], B: JsonDecoder]: JsonDecoder[A] = + JsonDecoder[B].map(_.coerce[A]) + + implicit def coercibleEncoder[A: Coercible[*, B], B: JsonEncoder]: JsonEncoder[A] = + JsonEncoder[B].contramap(_.coerce[B]) + + implicit def coercibleKeyDecoder[A: Coercible[B, *], B: JsonFieldDecoder]: JsonFieldDecoder[A] = + JsonFieldDecoder[B].map(_.coerce[A]) + + implicit def coercibleKeyEncoder[A: Coercible[*, B], B: JsonFieldEncoder]: JsonFieldEncoder[A] = + JsonFieldEncoder[B].contramap(_.coerce[B]) +} diff --git a/zio-json-interop-newtype/shared/src/test/scala/zio/json/interop/newtype/NewtypeSpec.scala b/zio-json-interop-newtype/shared/src/test/scala/zio/json/interop/newtype/NewtypeSpec.scala new file mode 100644 index 00000000..b03933d2 --- /dev/null +++ b/zio-json-interop-newtype/shared/src/test/scala/zio/json/interop/newtype/NewtypeSpec.scala @@ -0,0 +1,22 @@ +package zio.json.interop.newtype + +import io.estatico.newtype.macros.newtype +import zio.json.{ DecoderOps, DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder } +import zio.test.Assertion.{ equalTo, isRight } +import zio.test.{ Spec, ZIOSpecDefault, assert, assertTrue } + +object NewtypeSpec extends ZIOSpecDefault { + override def spec: Spec[Environment, Any] = suite("newtype")( + test("newtype") { + assert("""{"name":"fommil"}""".fromJson[Person])(isRight(equalTo(Person(Name("fommil"))))) && + assertTrue(Person(Name("fommil")).toJson == """{"name":"fommil"}""") + } + ) + + @newtype case class Name(value: String) + case class Person(name: Name) + object Person { + implicit val encoder: JsonEncoder[Person] = DeriveJsonEncoder.gen[Person] + implicit val decoder: JsonDecoder[Person] = DeriveJsonDecoder.gen[Person] + } +}