diff --git a/build.sbt b/build.sbt index 9e7e6ba51..83ad4a294 100644 --- a/build.sbt +++ b/build.sbt @@ -640,8 +640,10 @@ lazy val zio2 = (project in file("zio-2")) name := "zio-2", scalaVersion := scala3Version, libraryDependencies += "dev.zio" %% "zio" % zioVersion, + libraryDependencies += "dev.zio" %% "zio-json" % "0.6.2", libraryDependencies += "dev.zio" %% "zio-test" % zioVersion % Test, libraryDependencies += "dev.zio" %% "zio-test-sbt" % zioVersion % Test, + libraryDependencies ++= scalaTestDeps, testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") ) diff --git a/zio-2/src/main/scala/com/baeldung/scala/zio/json/BaeldungZIOJson.scala b/zio-2/src/main/scala/com/baeldung/scala/zio/json/BaeldungZIOJson.scala new file mode 100644 index 000000000..525e1ddfc --- /dev/null +++ b/zio-2/src/main/scala/com/baeldung/scala/zio/json/BaeldungZIOJson.scala @@ -0,0 +1,29 @@ +package com.baeldung.scala.zio.json + +import zio.json.* + +sealed trait Command +case class Start(timeout: Long) extends Command +case object Stop extends Command +case class Kill(reason: String, force: Boolean) extends Command + +@jsonDiscriminator("type") +sealed trait Command2 +case class Start2(timeout: Long) extends Command2 +case object Stop2 extends Command2 +case class Kill2(reason: String, force: Boolean) extends Command2 + +object Start { + implicit val encoder: JsonEncoder[Start] = DeriveJsonEncoder.gen[Start] + implicit val decoder: JsonDecoder[Start] = DeriveJsonDecoder.gen[Start] +} + +object Command { + implicit val encoder: JsonEncoder[Command] = DeriveJsonEncoder.gen[Command] + implicit val decoder: JsonDecoder[Command] = DeriveJsonDecoder.gen[Command] +} + +object Command2 { + implicit val encoder: JsonEncoder[Command2] = DeriveJsonEncoder.gen[Command2] + implicit val decoder: JsonDecoder[Command2] = DeriveJsonDecoder.gen[Command2] +} diff --git a/zio-2/src/test/scala/com/baeldung/scala/zio/json/DecodingUnitTest.scala b/zio-2/src/test/scala/com/baeldung/scala/zio/json/DecodingUnitTest.scala new file mode 100644 index 000000000..b0e2a17a7 --- /dev/null +++ b/zio-2/src/test/scala/com/baeldung/scala/zio/json/DecodingUnitTest.scala @@ -0,0 +1,65 @@ +package com.baeldung.scala.zio.json + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import zio.json.* + +class DecodingUnitTest extends AnyWordSpec with Matchers { + "zio-json" should { + "decode a JSON object to a case class" in { + """{"timeout":789}""".fromJson[Start] shouldBe Right(Start(789)) + } + + "return a decoding error if the JSON is not correct" in { + """{"duration":789}""".fromJson[Start] shouldBe Left(".timeout(missing)") + } + + "decode a JSON object with extra fields to a case class" in { + """{"timeout":789, "extra": "field"}""".fromJson[Start] shouldBe Right( + Start(789) + ) + } + + "decode an ADT to JSON" in { + """{"Start":{"timeout":100}}""".fromJson[Command] shouldBe Right( + Start(100) + ) + """{"Stop":{}}""".fromJson[Command] shouldBe Right(Stop) + """{"Kill":{"reason":"Random reason","force":false}}""" + .fromJson[Command] shouldBe Right( + Kill("Random reason", false) + ) + } + + "decode Stop to JSON" in { + implicit val decoder: JsonDecoder[Stop.type] = + implicitly[JsonDecoder[String]].map(_ => Stop) + + """{"Start":{"timeout":100}}""".fromJson[Command] shouldBe Right( + Start(100) + ) + """"Stop"""".fromJson[Stop.type] shouldBe Right(Stop) + """{"Kill":{"reason":"Random reason","force":false}}""" + .fromJson[Command] shouldBe Right( + Kill("Random reason", false) + ) + } + + "use a discriminator" in { + """{"type":"Start2","timeout":100}""".fromJson[Command2] shouldBe Right( + Start2(100) + ) + """{"type":"Stop2"}""".fromJson[Command2] shouldBe Right(Stop2) + """{"type":"Kill2","reason":"Random reason","force":false}""" + .fromJson[Command2] shouldBe Right( + Kill2("Random reason", false) + ) + } + + "fail if there's no discriminator" in { + """{"timeout":100}""".fromJson[Command2] shouldBe Left( + "(missing hint 'type')" + ) + } + } +} diff --git a/zio-2/src/test/scala/com/baeldung/scala/zio/json/EncodingUnitTest.scala b/zio-2/src/test/scala/com/baeldung/scala/zio/json/EncodingUnitTest.scala new file mode 100644 index 000000000..c6c547e3c --- /dev/null +++ b/zio-2/src/test/scala/com/baeldung/scala/zio/json/EncodingUnitTest.scala @@ -0,0 +1,45 @@ +package com.baeldung.scala.zio.json + +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import zio.json.* + +class EncodingUnitTest extends AnyWordSpec with Matchers { + "zio-json" should { + "encode a case class to JSON" in { + Start(100).toJson shouldBe """{"timeout":100}""" + } + + "encode an ADT to JSON" in { + (Start(100): Command).toJson shouldBe """{"Start":{"timeout":100}}""" + (Stop: Command).toJson shouldBe """{"Stop":{}}""" + (Kill( + reason = "Random reason", + force = false + ): Command).toJson shouldBe """{"Kill":{"reason":"Random reason","force":false}}""" + } + + "encode Stop to JSON" in { + implicit val encoder: JsonEncoder[Stop.type] = + implicitly[JsonEncoder[String]].contramap(_.toString()) + + (Start(100): Command).toJson shouldBe """{"Start":{"timeout":100}}""" + Stop.toJson shouldBe """"Stop"""" + (Kill( + reason = "Random reason", + force = false + ): Command).toJson shouldBe """{"Kill":{"reason":"Random reason","force":false}}""" + } + + "use a discriminator" in { + (Start2( + 100 + ): Command2).toJson shouldBe """{"type":"Start2","timeout":100}""" + (Stop2: Command2).toJson shouldBe """{"type":"Stop2"}""" + (Kill2( + reason = "Random reason", + force = false + ): Command2).toJson shouldBe """{"type":"Kill2","reason":"Random reason","force":false}""" + } + } +}