diff --git a/build.sbt b/build.sbt index aa577ae1..64e5ec84 100644 --- a/build.sbt +++ b/build.sbt @@ -32,12 +32,12 @@ addCommandAlias( addCommandAlias( "testScala2JVM", - "zioJsonMacrosJVM/test; zioJsonInteropRefinedJVM/test" + "zioJsonMacrosJVM/test; zioJsonInteropRefinedJVM/test; zioJsonInteropNewtypeJVM/test" ) addCommandAlias( "testScala2JS", - "zioJsonMacrosJS/test; zioJsonInteropRefinedJS/test" + "zioJsonMacrosJS/test; zioJsonInteropRefinedJS/test; zioJsonInteropNewtypeJS/test" ) addCommandAlias( @@ -79,6 +79,8 @@ lazy val zioJsonRoot = project zioJsonInteropScalaz7x.js, zioJsonInteropScalaz7x.jvm, zioJsonInteropScalaz7x.native, + zioJsonInteropNewtype.js, + zioJsonInteropNewtype.jvm, zioJsonGolden ) @@ -357,6 +359,23 @@ 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" + ), + scalacOptions += "-language:implicitConversions", + testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") + ) + .enablePlugins(BuildInfoPlugin) + lazy val zioJsonInteropScalaz7x = crossProject(JSPlatform, JVMPlatform, NativePlatform) .in(file("zio-json-interop-scalaz7x")) .dependsOn(zioJson) @@ -382,7 +401,8 @@ lazy val docs = project zioJsonMacrosJVM, zioJsonInteropHttp4s, zioJsonInteropRefined.jvm, - zioJsonInteropScalaz7x.jvm + zioJsonInteropScalaz7x.jvm, + zioJsonInteropNewtype.jvm ) .settings( crossScalaVersions -= ScalaDotty, @@ -398,6 +418,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..e439e1e8 --- /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[B: JsonDecoder, A](implicit coerce: Coercible[B, A]): JsonDecoder[A] = + JsonDecoder[B].map(_.coerce[A]) + + implicit def coercibleEncoder[B: JsonEncoder, A](implicit coerce: Coercible[A, B]): JsonEncoder[A] = + JsonEncoder[B].contramap(_.coerce[B]) + + implicit def coercibleKeyDecoder[B: JsonFieldDecoder, A](implicit coerce: Coercible[B, A]): JsonFieldDecoder[A] = + JsonFieldDecoder[B].map(_.coerce[A]) + + implicit def coercibleKeyEncoder[B: JsonFieldEncoder, A](implicit coerce: Coercible[A, B]): 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] + } +}