From 8cbd5fb1c7f5632adf20a928c6e8317326e8f9d7 Mon Sep 17 00:00:00 2001 From: Simon Popugaev Date: Mon, 5 Dec 2022 10:29:05 +0300 Subject: [PATCH] fix to ast name transform (#813) --- .../src/main/scala-2.x/zio/json/macros.scala | 2 +- .../src/main/scala-3/zio/json/macros.scala | 2 +- .../json/ConfigurableDeriveCodecSpec.scala | 216 ++++++++++++------ .../src/test/scala/zio/json/CodecSpec.scala | 6 + 4 files changed, 155 insertions(+), 71 deletions(-) diff --git a/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala b/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala index a58c41c2e..1049297b6 100644 --- a/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala +++ b/zio-json/shared/src/main/scala-2.x/zio/json/macros.scala @@ -510,7 +510,7 @@ object DeriveJsonEncoder { .foldLeft[Either[String, Chunk[(String, Json)]]](Right(Chunk.empty)) { case (c, param) => val name = param.annotations.collectFirst { case jsonField(name) => name - }.getOrElse(param.label) + }.getOrElse(nameTransform(param.label)) c.flatMap { chunk => param.typeclass.toJsonAST(param.dereference(a)).map { value => if (value == Json.Null) chunk diff --git a/zio-json/shared/src/main/scala-3/zio/json/macros.scala b/zio-json/shared/src/main/scala-3/zio/json/macros.scala index a263f66f9..3dc34d338 100644 --- a/zio-json/shared/src/main/scala-3/zio/json/macros.scala +++ b/zio-json/shared/src/main/scala-3/zio/json/macros.scala @@ -512,7 +512,7 @@ object DeriveJsonEncoder extends Derivation[JsonEncoder] { self => .foldLeft[Either[String, Chunk[(String, Json)]]](Right(Chunk.empty)) { case (c, param) => val name = param.annotations.collectFirst { case jsonField(name) => name - }.getOrElse(param.label) + }.getOrElse(nameTransform(param.label)) c.flatMap { chunk => param.typeclass.toJsonAST(param.deref(a)).map { value => if (value == Json.Null) chunk diff --git a/zio-json/shared/src/test/scala-2.x/zio/json/ConfigurableDeriveCodecSpec.scala b/zio-json/shared/src/test/scala-2.x/zio/json/ConfigurableDeriveCodecSpec.scala index db3902311..b61ff8485 100644 --- a/zio-json/shared/src/test/scala-2.x/zio/json/ConfigurableDeriveCodecSpec.scala +++ b/zio-json/shared/src/test/scala-2.x/zio/json/ConfigurableDeriveCodecSpec.scala @@ -1,6 +1,7 @@ package zio.json import zio.json.JsonCodecConfiguration.SumTypeHandling.DiscriminatorField +import zio.json.ast.Json import zio.test._ object ConfigurableDeriveCodecSpec extends ZIOSpecDefault { @@ -15,77 +16,154 @@ object ConfigurableDeriveCodecSpec extends ZIOSpecDefault { def spec = suite("ConfigurableDeriveCodecSpec")( suite("defaults")( - test("should not map field names by default") { - val expectedStr = """{"someField":1,"someOtherField":"a"}""" - val expectedObj = ClassWithFields(1, "a") - - implicit val codec: JsonCodec[ClassWithFields] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[ClassWithFields].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("should not use discriminator by default") { - val expectedStr = """{"CaseObj":{}}""" - val expectedObj: ST = ST.CaseObj - - implicit val codec: JsonCodec[ST] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[ST].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("should allow extra fields by default") { - val jsonStr = """{"someField":1,"someOtherField":"a","extra":123}""" - val expectedObj = ClassWithFields(1, "a") - - implicit val codec: JsonCodec[ClassWithFields] = DeriveJsonCodec.gen - - assertTrue( - jsonStr.fromJson[ClassWithFields].toOption.get == expectedObj - ) - } + suite("string")( + test("should not map field names by default") { + val expectedStr = """{"someField":1,"someOtherField":"a"}""" + val expectedObj = ClassWithFields(1, "a") + + implicit val codec: JsonCodec[ClassWithFields] = DeriveJsonCodec.gen + + assertTrue( + expectedStr.fromJson[ClassWithFields].toOption.get == expectedObj, + expectedObj.toJson == expectedStr + ) + }, + test("should not use discriminator by default") { + val expectedStr = """{"CaseObj":{}}""" + val expectedObj: ST = ST.CaseObj + + implicit val codec: JsonCodec[ST] = DeriveJsonCodec.gen + + assertTrue( + expectedStr.fromJson[ST].toOption.get == expectedObj, + expectedObj.toJson == expectedStr + ) + }, + test("should allow extra fields by default") { + val jsonStr = """{"someField":1,"someOtherField":"a","extra":123}""" + val expectedObj = ClassWithFields(1, "a") + + implicit val codec: JsonCodec[ClassWithFields] = DeriveJsonCodec.gen + + assertTrue( + jsonStr.fromJson[ClassWithFields].toOption.get == expectedObj + ) + } + ), + suite("AST")( + test("should not map field names by default") { + val expectedAST = Json.Obj("someField" -> Json.Num(1), "someOtherField" -> Json.Str("a")) + val expectedObj = ClassWithFields(1, "a") + + implicit val codec: JsonCodec[ClassWithFields] = DeriveJsonCodec.gen + + assertTrue( + expectedAST.as[ClassWithFields].toOption.get == expectedObj, + expectedObj.toJsonAST.toOption.get == expectedAST + ) + }, + test("should not use discriminator by default") { + val expectedAST = Json.Obj("CaseObj" -> Json.Obj()) + val expectedObj: ST = ST.CaseObj + + implicit val codec: JsonCodec[ST] = DeriveJsonCodec.gen + + assertTrue( + expectedAST.as[ST].toOption.get == expectedObj, + expectedObj.toJsonAST.toOption.get == expectedAST + ) + }, + test("should allow extra fields by default") { + val jsonAST = Json.Obj("someField" -> Json.Num(1), "someOtherField" -> Json.Str("a"), "extra" -> Json.Num(1)) + val expectedObj = ClassWithFields(1, "a") + + implicit val codec: JsonCodec[ClassWithFields] = DeriveJsonCodec.gen + + assertTrue( + jsonAST.as[ClassWithFields].toOption.get == expectedObj + ) + } + ) ), suite("overrides")( - test("should override field name mapping") { - val expectedStr = """{"some_field":1,"some_other_field":"a"}""" - val expectedObj = ClassWithFields(1, "a") - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(fieldNameMapping = SnakeCase) - implicit val codec: JsonCodec[ClassWithFields] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[ClassWithFields].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("should specify discriminator") { - val expectedStr = """{"$type":"CaseClass","i":1}""" - val expectedObj: ST = ST.CaseClass(i = 1) - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(sumTypeHandling = DiscriminatorField("$type")) - implicit val codec: JsonCodec[ST] = DeriveJsonCodec.gen - - assertTrue( - expectedStr.fromJson[ST].toOption.get == expectedObj, - expectedObj.toJson == expectedStr - ) - }, - test("should prevent extra fields") { - val jsonStr = """{"someField":1,"someOtherField":"a","extra":123}""" - - implicit val config: JsonCodecConfiguration = - JsonCodecConfiguration(allowExtraFields = false) - implicit val codec: JsonCodec[ClassWithFields] = DeriveJsonCodec.gen - - assertTrue( - jsonStr.fromJson[ClassWithFields].isLeft - ) - } + suite("string")( + test("should override field name mapping") { + val expectedStr = """{"some_field":1,"some_other_field":"a"}""" + val expectedObj = ClassWithFields(1, "a") + + implicit val config: JsonCodecConfiguration = + JsonCodecConfiguration(fieldNameMapping = SnakeCase) + implicit val codec: JsonCodec[ClassWithFields] = DeriveJsonCodec.gen + + assertTrue( + expectedStr.fromJson[ClassWithFields].toOption.get == expectedObj, + expectedObj.toJson == expectedStr + ) + }, + test("should specify discriminator") { + val expectedStr = """{"$type":"CaseClass","i":1}""" + val expectedObj: ST = ST.CaseClass(i = 1) + + implicit val config: JsonCodecConfiguration = + JsonCodecConfiguration(sumTypeHandling = DiscriminatorField("$type")) + implicit val codec: JsonCodec[ST] = DeriveJsonCodec.gen + + assertTrue( + expectedStr.fromJson[ST].toOption.get == expectedObj, + expectedObj.toJson == expectedStr + ) + }, + test("should prevent extra fields") { + val jsonStr = """{"someField":1,"someOtherField":"a","extra":123}""" + + implicit val config: JsonCodecConfiguration = + JsonCodecConfiguration(allowExtraFields = false) + implicit val codec: JsonCodec[ClassWithFields] = DeriveJsonCodec.gen + + assertTrue( + jsonStr.fromJson[ClassWithFields].isLeft + ) + } + ), + suite("AST")( + test("should override field name mapping") { + val expectedAST = Json.Obj("some_field" -> Json.Num(1), "some_other_field" -> Json.Str("a")) + val expectedObj = ClassWithFields(1, "a") + + implicit val config: JsonCodecConfiguration = + JsonCodecConfiguration(fieldNameMapping = SnakeCase) + implicit val codec: JsonCodec[ClassWithFields] = DeriveJsonCodec.gen + + assertTrue( + expectedAST.as[ClassWithFields].toOption.get == expectedObj, + expectedObj.toJsonAST.toOption.get == expectedAST + ) + }, + test("should specify discriminator") { + val expectedAST = Json.Obj("$type" -> Json.Str("CaseClass"), "i" -> Json.Num(1)) + val expectedObj: ST = ST.CaseClass(i = 1) + + implicit val config: JsonCodecConfiguration = + JsonCodecConfiguration(sumTypeHandling = DiscriminatorField("$type")) + implicit val codec: JsonCodec[ST] = DeriveJsonCodec.gen + + assertTrue( + expectedAST.as[ST].toOption.get == expectedObj, + expectedObj.toJsonAST.toOption.get == expectedAST + ) + }, + test("should prevent extra fields") { + val jsonAST = Json.Obj("someField" -> Json.Num(1), "someOtherField" -> Json.Str("a"), "extra" -> Json.Num(1)) + + implicit val config: JsonCodecConfiguration = + JsonCodecConfiguration(allowExtraFields = false) + implicit val codec: JsonCodec[ClassWithFields] = DeriveJsonCodec.gen + + assertTrue( + jsonAST.as[ClassWithFields].isLeft + ) + } + ) ) ) } diff --git a/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala b/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala index b5bdae7b2..8c0077a91 100644 --- a/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala +++ b/zio-json/shared/src/test/scala/zio/json/CodecSpec.scala @@ -2,6 +2,7 @@ package testzio.json import zio._ import zio.json._ +import zio.json.ast.Json import zio.test.Assertion._ import zio.test._ @@ -96,10 +97,15 @@ object CodecSpec extends ZIOSpecDefault { assert(indianaJones.fromJson[Custom])(isRight(equalTo(Custom("")))) && assert(overrides.fromJson[OverridesAlsoWork])(isRight(equalTo(OverridesAlsoWork("", 0)))) && assertTrue(Kebabed("").toJson == kebabed) && + assertTrue(Kebabed("").toJsonAST.toOption.get == kebabed.fromJson[Json].toOption.get) && assertTrue(Snaked("").toJson == snaked) && + assertTrue(Snaked("").toJsonAST.toOption.get == snaked.fromJson[Json].toOption.get) && assertTrue(Pascaled("").toJson == pascaled) && + assertTrue(Pascaled("").toJsonAST.toOption.get == pascaled.fromJson[Json].toOption.get) && assertTrue(Cameled("").toJson == cameled) && + assertTrue(Cameled("").toJsonAST.toOption.get == cameled.fromJson[Json].toOption.get) && assertTrue(Custom("").toJson == indianaJones) && + assertTrue(Custom("").toJsonAST.toOption.get == indianaJones.fromJson[Json].toOption.get) && assertTrue(OverridesAlsoWork("", 0).toJson == overrides) }, test("unicode") {