From 1f9f3c135dd89bdb7d7feb4bb7c39be2f88736b1 Mon Sep 17 00:00:00 2001 From: Alberto Paro Date: Mon, 12 Aug 2019 12:44:41 +0200 Subject: [PATCH 1/8] Added default value management and sealed trait to derivation macro --- .../annotations/Configuration.scala | 50 +- .../derivation/annotations/JsonCodec.scala | 19 +- .../derivation/annotations/JsonKey.scala | 7 + .../annotations/JsonCodecADTSpec.scala | 81 +++ .../annotations/JsonCodecCustomSpec.scala | 71 ++ .../annotations/JsonCodecMacrosSuite.scala | 52 +- .../circe/derivation/DerivationMacros.scala | 619 ++++++++++++++++-- .../io/circe/derivation/Discriminator.scala | 12 + .../scala/io/circe/derivation/package.scala | 19 +- .../NameTransformationExample.scala | 21 +- .../derivation/NameTransformationSuite.scala | 29 +- 11 files changed, 871 insertions(+), 109 deletions(-) create mode 100644 modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonKey.scala create mode 100644 modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecADTSpec.scala create mode 100644 modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecCustomSpec.scala create mode 100644 modules/derivation/shared/src/main/scala/io/circe/derivation/Discriminator.scala diff --git a/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/Configuration.scala b/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/Configuration.scala index 707fac1b..f02b9b4d 100644 --- a/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/Configuration.scala +++ b/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/Configuration.scala @@ -11,6 +11,7 @@ import io.circe.derivation.renaming * derived - or if only one of them will be. */ sealed trait Configuration { + import io.circe.derivation.Discriminator type Config <: Configuration @@ -19,8 +20,14 @@ sealed trait Configuration { */ def transformMemberNames: String => String + def useDefaults: Boolean + + def discriminator: Discriminator + protected def getA(transformMemberNames: String => String): Config + protected def applyDiscriminator(discriminator: Discriminator): Config + /** Creates a configuration which produces snake cased member names */ final def withSnakeCaseMemberNames: Config = getA(renaming.snakeCase) @@ -28,9 +35,16 @@ sealed trait Configuration { /** Creates a configuration which produces kebab cased member names */ final def withKebabCaseMemberNames: Config = getA(renaming.kebabCase) + + final def withDiscriminatorName(name: String): Config = + applyDiscriminator(Discriminator.Embedded(name)) + + final def withTypeDiscriminator: Config = + applyDiscriminator(Discriminator.TypeDiscriminator) } object Configuration { + import io.circe.derivation.Discriminator /** Configuration allowing customisation of JSON produced when encoding or * decoding. @@ -38,13 +52,20 @@ object Configuration { * This configuration creates *both* encoder and decoder. */ final case class Codec( - transformMemberNames: String => String + transformMemberNames: String => String, + useDefaults: Boolean, + discriminator: Discriminator ) extends Configuration { type Config = Codec protected final def getA(transformMemberNames: String => String) = - Codec(transformMemberNames) + Codec(transformMemberNames, useDefaults, discriminator) + + protected final def applyDiscriminator( + discriminator: Discriminator + ): Codec = + Codec(transformMemberNames, useDefaults, discriminator) } /** Configuration allowing customisation of JSON produced when encoding or @@ -53,13 +74,18 @@ object Configuration { * This configuration **only** creates decoder. */ final case class DecodeOnly( - transformMemberNames: String => String + transformMemberNames: String => String, + useDefaults: Boolean, + discriminator: Discriminator ) extends Configuration { type Config = DecodeOnly protected final def getA(transformMemberNames: String => String) = - DecodeOnly(transformMemberNames) + DecodeOnly(transformMemberNames, useDefaults, discriminator) + + protected final def applyDiscriminator(discriminator: Discriminator) = + DecodeOnly(transformMemberNames, useDefaults, discriminator) } /** Configuration allowing customisation of JSON produced when encoding or @@ -68,24 +94,30 @@ object Configuration { * This configuration **only** creates encoder. */ final case class EncodeOnly( - transformMemberNames: String => String + transformMemberNames: String => String, + useDefaults: Boolean, + discriminator: Discriminator ) extends Configuration { type Config = EncodeOnly protected final def getA(transformMemberNames: String => String) = - EncodeOnly(transformMemberNames) + EncodeOnly(transformMemberNames, useDefaults, discriminator) + + protected final def applyDiscriminator(discriminator: Discriminator) = + EncodeOnly(transformMemberNames, useDefaults, discriminator) + } /** Create a default configuration with both decoder and encoder */ val default: Codec = - Codec(identity) + Codec(identity, true, Discriminator.default) /** Create a default configuration with **only** encoder */ val encodeOnly: EncodeOnly = - EncodeOnly(identity) + EncodeOnly(identity, true, Discriminator.default) /** Create a default configuration with **only** decoder */ val decodeOnly: DecodeOnly = - DecodeOnly(identity) + DecodeOnly(identity, true, Discriminator.default) } diff --git a/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala b/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala index 2f70be82..e4e6993b 100644 --- a/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala +++ b/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala @@ -87,11 +87,11 @@ private[derivation] final class GenericJsonCodecMacros(val c: blackbox.Context) private[this] def codecFrom(tree: Tree): JsonCodecType = tree.tpe.dealias match { - case t if t <:< typeOf[Configuration.Codec] => + case t if t == typeOf[Configuration.Codec] => JsonCodecType.Both - case t if t <:< typeOf[Configuration.DecodeOnly] => + case t if t == typeOf[Configuration.DecodeOnly] => JsonCodecType.DecodeOnly - case t if t <:< typeOf[Configuration.EncodeOnly] => + case t if t == typeOf[Configuration.EncodeOnly] => JsonCodecType.EncodeOnly case t => c.warning( @@ -103,6 +103,13 @@ private[derivation] final class GenericJsonCodecMacros(val c: blackbox.Context) private[this] val cfgNameTransformation = q"$config.transformMemberNames" + private[this] val cfgUseDefaults = + q"$config.useDefaults" + private[this] val cfgDiscriminator = + q"$config.discriminator" + + private[this] val defaultDiscriminator: Tree = + q"_root_.io.circe.derivation.Discriminator.default" private[this] def codec(clsDef: ClassDef): Tree = { val tpname = clsDef.name @@ -114,11 +121,11 @@ private[derivation] final class GenericJsonCodecMacros(val c: blackbox.Context) val Type = tpname ( q"""implicit val $decoderName: $DecoderClass[$Type] = - _root_.io.circe.derivation.deriveDecoder[$Type]($cfgNameTransformation)""", + _root_.io.circe.derivation.deriveDecoder[$Type]($cfgNameTransformation, $cfgUseDefaults, $cfgDiscriminator)""", q"""implicit val $encoderName: $AsObjectEncoderClass[$Type] = - _root_.io.circe.derivation.deriveEncoder[$Type]($cfgNameTransformation)""", + _root_.io.circe.derivation.deriveEncoder[$Type]($cfgNameTransformation, $cfgDiscriminator)""", q"""implicit val $codecName: $AsObjectCodecClass[$Type] = - _root_.io.circe.derivation.deriveCodec[$Type]($cfgNameTransformation)""" + _root_.io.circe.derivation.deriveCodec[$Type]($cfgNameTransformation, $cfgUseDefaults, $cfgDiscriminator)""" ) } else { val tparamNames = tparams.map(_.name) diff --git a/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonKey.scala b/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonKey.scala new file mode 100644 index 00000000..d704af2c --- /dev/null +++ b/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonKey.scala @@ -0,0 +1,7 @@ +package io.circe.derivation.annotations + +import scala.annotation.StaticAnnotation + +final case class JsonKey(value: String) extends StaticAnnotation + +final case class JsonNoDefault() extends StaticAnnotation \ No newline at end of file diff --git a/modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecADTSpec.scala b/modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecADTSpec.scala new file mode 100644 index 00000000..876074eb --- /dev/null +++ b/modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecADTSpec.scala @@ -0,0 +1,81 @@ +package io.circe.derivation.annotations + +import io.circe._ +import io.circe.parser._ +import io.circe.syntax._ +import org.scalatest._ + +object JsonCodecADTSpecSamples { + + @JsonCodec + sealed trait ADT1 + + @JsonCodec case class ADT1A(a: Int) extends ADT1 + @JsonCodec case class ADT1B(b: Int) extends ADT1 + + @JsonCodec(Configuration.default.withDiscriminatorName("_type")) + sealed trait ADT1Custom + + @JsonCodec case class ADT1CustomA(a: Int) extends ADT1Custom + @JsonCodec case class ADT1CustomB(b: Int) extends ADT1Custom + + @JsonCodec(Configuration.default.withTypeDiscriminator) + sealed trait ADTTyped + + @JsonCodec case class ADTTypedA(a: Int) extends ADTTyped + @JsonCodec case class ADTTypedB(b: Int) extends ADTTyped +} + +class JsonCodecADTSpec extends WordSpec with Matchers { + + import JsonCodecADTSpecSamples._ + + implicit val printer = Printer.noSpaces.copy(dropNullValues = true) + + "JsonCodecADTSpec" should { + + "serialize default" in { + val a1: ADT1 = ADT1A(1) + + a1.asJson.pretty(printer) should be("""{"a":1,"type":"adt1a"}""") + parse("""{"a":1,"type":"adt1a"}""").right.get.as[ADT1] should be( + Right(a1) + ) + + val b1: ADT1 = ADT1B(1) + + b1.asJson.pretty(printer) should be("""{"b":1,"type":"adt1b"}""") + parse("""{"b":1,"type":"adt1b"}""").right.get.as[ADT1] should be( + Right(b1) + ) + } + + "serialize discriminator custom fieldname" in { + val a1: ADT1Custom = ADT1CustomA(1) + + a1.asJson.pretty(printer) should be("""{"a":1,"_type":"adt1customa"}""") + parse("""{"a":1,"_type":"adt1customa"}""").right.get.as[ADT1Custom] should be(Right(a1)) + + val b1: ADT1Custom = ADT1CustomB(1) + + b1.asJson.pretty(printer) should be("""{"b":1,"_type":"adt1customb"}""") + parse("""{"b":1,"_type":"adt1customb"}""").right.get.as[ADT1Custom] should be(Right(b1)) + } + + "serialize discriminator typed" in { + val a1: ADTTyped = ADTTypedA(1) + + a1.asJson.pretty(printer) should be("""{"adttypeda":{"a":1}}""") + parse("""{"adttypeda":{"a":1}}""").right.get.as[ADTTyped] should be( + Right(a1) + ) + + val b1: ADTTyped = ADTTypedB(1) + + b1.asJson.pretty(printer) should be("""{"adttypedb":{"b":1}}""") + parse("""{"adttypedb":{"b":1}}""").right.get.as[ADTTyped] should be( + Right(b1) + ) + } + } +} diff --git a/modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecCustomSpec.scala b/modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecCustomSpec.scala new file mode 100644 index 00000000..264af85e --- /dev/null +++ b/modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecCustomSpec.scala @@ -0,0 +1,71 @@ +package io.circe.derivation.annotations + +import io.circe._ +import io.circe.parser._ +import io.circe.syntax._ +import org.scalatest._ + +object CustomJsonCodecSpecSamples { + + @JsonCodec + case class Person( + @JsonKey("n") name: String, + @JsonNoDefault @JsonKey("a") age: Int = 0, + optional: Option[String] = None + ) + + @JsonCodec + case class RemoveEmptyPerson( + optional: Option[String] = None, + values: List[String] = Nil, + valuesSet: Set[String] = Set.empty[String], + obj: Json = Json.obj() + ) + + @JsonCodec + case class Group(name: String, team: Map[String, Person]) + +} + +class CustomJsonCodecSpec extends WordSpec with Matchers { + + import CustomJsonCodecSpecSamples._ + + implicit val printer = Printer.noSpaces.copy(dropNullValues = true) + + "CustomJsonCodec" should { + + io.circe.Decoder + "correct generate json" in { + val p1 = Person("andrea") + + p1.asJson.pretty(printer) should be("{\"n\":\"andrea\"}") + parse("""{"n":"andrea"}""").right.get.as[Person] should be(Right(p1)) + } + + "remove empty values" in { + val p1 = RemoveEmptyPerson() + + p1.asJson.pretty(printer) should be( + """{"values":[],"valuesSet":[],"obj":{}}""" + ) + + val p2 = RemoveEmptyPerson(values = List("a")) + + p2.asJson.pretty(printer) should be( + """{"values":["a"],"valuesSet":[],"obj":{}}""" + ) + parse("""{}""").right.get.as[RemoveEmptyPerson] should be(Right(p1)) + } + + "manage map" in { + val g1 = Group("name", Map("peter" -> Person("Peter", 18))) + + g1.asJson.pretty(printer) should be( + """{"name":"name","team":{"peter":{"n":"Peter","a":18}}}""" + ) + + } + + } +} diff --git a/modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecMacrosSuite.scala b/modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecMacrosSuite.scala index 89bd6885..31e6c772 100644 --- a/modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecMacrosSuite.scala +++ b/modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecMacrosSuite.scala @@ -76,28 +76,29 @@ package jsoncodecmacrossuiteaux { // Hierarchy -// @JsonCodec sealed trait Hierarchy -// final case class Hierarchy1(i: Int, s: String) extends Hierarchy -// final case class Hierarchy2(xs: List[String]) extends Hierarchy -// final case class Hierarchy3(s: Single, d: Double) extends Hierarchy -// -// object Hierarchy { -// implicit val eqHierarchy: Eq[Hierarchy] = Eq.fromUniversalEquals -// -// implicit val arbitraryHierarchy: Arbitrary[Hierarchy] = Arbitrary( -// Gen.oneOf( -// for { -// i <- Arbitrary.arbitrary[Int] -// s <- Arbitrary.arbitrary[String] -// } yield Hierarchy1(i, s), -// Gen.listOf(Arbitrary.arbitrary[String]).map(Hierarchy2.apply), -// for { -// s <- Arbitrary.arbitrary[Single] -// d <- Arbitrary.arbitrary[Double] -// } yield Hierarchy3(s, d) -// ) -// ) -// } + @JsonCodec + sealed trait Hierarchy + @JsonCodec final case class Hierarchy1(i: Int, s: String) extends Hierarchy + @JsonCodec final case class Hierarchy2(xs: List[String]) extends Hierarchy + @JsonCodec final case class Hierarchy3(s: Single, d: Double) extends Hierarchy + + object Hierarchy { + implicit val eqHierarchy: Eq[Hierarchy] = Eq.fromUniversalEquals + + implicit val arbitraryHierarchy: Arbitrary[Hierarchy] = Arbitrary( + Gen.oneOf( + for { + i <- Arbitrary.arbitrary[Int] + s <- Arbitrary.arbitrary[String] + } yield Hierarchy1(i, s), + Gen.listOf(Arbitrary.arbitrary[String]).map(Hierarchy2.apply), + for { + s <- Arbitrary.arbitrary[Single] + d <- Arbitrary.arbitrary[Double] + } yield Hierarchy3(s, d) + ) + ) + } // SelfRecursiveWithOption @@ -126,8 +127,11 @@ class JsonCodecMacrosSuite extends CirceSuite { checkLaws("Codec[Single]", CodecTests[Single].codec) checkLaws("Codec[Typed1[Int]]", CodecTests[Typed1[Int]].codec) checkLaws("Codec[Typed2[Int, Long]]", CodecTests[Typed2[Int, Long]].codec) -// checkLaws("Codec[Hierarchy]", CodecTests[Hierarchy].codec) - checkLaws("Codec[SelfRecursiveWithOption]", CodecTests[SelfRecursiveWithOption].codec) + checkLaws("Codec[Hierarchy]", CodecTests[Hierarchy].codec) + checkLaws( + "Codec[SelfRecursiveWithOption]", + CodecTests[SelfRecursiveWithOption].codec + ) "@JsonCodec" should "provide Encoder.AsObject instances" in { Encoder.AsObject[Simple] diff --git a/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala b/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala index 8a751f69..5bb83809 100644 --- a/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala +++ b/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala @@ -1,6 +1,8 @@ package io.circe.derivation import io.circe.{ Codec, Decoder, Encoder } + +import scala.collection.immutable.ListMap import scala.reflect.macros.blackbox class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { @@ -10,6 +12,9 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { private[this] val decoderSymbol: Symbol = c.symbolOf[Decoder.type] private[this] val encoderTC: Type = typeOf[Encoder[_]].typeConstructor private[this] val decoderTC: Type = typeOf[Decoder[_]].typeConstructor + private[this] val defaultDiscriminator = + c.Expr[Discriminator](q"_root_.io.circe.derivation.Discriminator.default") + private[this] val trueExpression = c.Expr[Boolean](q"true") private[this] def failWithMessage(message: String): Nothing = c.abort(c.enclosingPosition, message) @@ -21,22 +26,73 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { } private[this] case class Instances(tpe: Type, encoder: Instance, decoder: Instance) - private[this] case class Member(name: TermName, decodedName: String, tpe: Type) + private[this] case class Member( + name: TermName, + decodedName: String, + tpe: Type, + keyName: Option[Tree], + default: Option[Tree], + noDefaultValue: Boolean + ) private[this] object Member { - final def fromSymbol(tpe: Type)(sym: Symbol): Member = { + final def fromSymbol(tpe: Type, defaults: ListMap[String, Option[Tree]])( + memberPosition: (Symbol, Int) + ): Member = { + val (sym, position) = memberPosition val memberName = sym.name val memberDecl = tpe.decl(memberName) + val default = { + val value = memberName.decodedName.toString + if (defaults.contains(value)) defaults(value) else None + } + if (!memberDecl.isMethod) failWithMessage( s"No method $memberName in $tpe (this is probably because a constructor parameter isn't a val)" ) + //we extract annotation names + var keyName: Option[Tree] = None + var noDefault = false + + sym.annotations.foreach { ann => + ann.tree match { + case Apply(Select(myType2, _), List(value)) => + // using string: ugly but fast + myType2.toString().split('.').last match { + case "JsonKey" => + var realValue = value.toString + realValue = realValue.substring(1, realValue.length - 1) + if (realValue.toString.isEmpty) + c.abort( + c.enclosingPosition, + s"Invalid empty key in $tpe.$sym!" + ) + keyName = Some(value) + case "JsonNoDefault" => + noDefault = true + case _ => + // we skip other annotations + } + case extra => + extra.toString().split('.').last match { + case "JsonNoDefault()" => + noDefault = true + case _ => + // we skip other annotations + } + } + } + Member( memberName.toTermName, memberName.decodedName.toString, - memberDecl.asMethod.returnType.asSeenFrom(tpe, tpe.typeSymbol) + memberDecl.asMethod.returnType.asSeenFrom(tpe, tpe.typeSymbol), + keyName, + default, + noDefault ) } } @@ -48,7 +104,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { def instantiateAccumulating: Tree = instantiate( paramListsWithNames.map( _.map { - case (Member(_, _, tpe), name) => extractFromValid(name, tpe) + case (Member(_, _, tpe, _, _, _), name) => extractFromValid(name, tpe) } ) ) @@ -72,7 +128,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { val instances: List[Instances] = paramLists.flatten.zipWithIndex .foldLeft(List.empty[Instances]) { - case (acc, (Member(_, _, tpe), i)) if acc.find(_.tpe =:= tpe).isEmpty => + case (acc, (Member(_, _, tpe, _, _, _), i)) if acc.find(_.tpe =:= tpe).isEmpty => val instances = Instances( tpe, Instance(encoderTC, tpe, TermName(s"encoder$i")), @@ -101,10 +157,15 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { private[this] def membersFromCompanionApply(tpe: Type): Option[ProductRepr] = tpe.companion.decl(applyName) match { case NoSymbol => None case s => + val defaults = caseClassFieldsDefaults(tpe) s.alternatives.collect { case m: MethodSymbol => m.paramLists }.sortBy(-_.map(_.size).sum).headOption.map { applyParams => - ProductReprWithApply(tpe, applyParams.map(_.map(Member.fromSymbol(tpe)))) + //We use zipwithIndex for gathering default field value if available + ProductReprWithApply( + tpe, + applyParams.map(_.zipWithIndex.map(Member.fromSymbol(tpe, defaults))) + ) } } @@ -112,9 +173,15 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { if (tpe.typeSymbol.isAbstract) { None } else { + val defaults = caseClassFieldsDefaults(tpe) tpe.decls.collectFirst { case m: MethodSymbol if m.isPrimaryConstructor && m.isPublic && !m.isAbstract => - ProductReprWithConstr(tpe, m.paramLists.map(_.map(Member.fromSymbol(tpe)))) + ProductReprWithConstr( + tpe, + m.paramLists.map( + _.zipWithIndex.map(Member.fromSymbol(tpe, defaults)) + ) + ) } } @@ -153,28 +220,150 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { ].a """ - def materializeDecoder[T: c.WeakTypeTag]: c.Expr[Decoder[T]] = materializeDecoderImpl[T](None) - def materializeEncoder[T: c.WeakTypeTag]: c.Expr[Encoder.AsObject[T]] = materializeEncoderImpl[T](None) - def materializeCodec[T: c.WeakTypeTag]: c.Expr[Codec.AsObject[T]] = materializeCodecImpl[T](None) + def materializeDecoder[T: c.WeakTypeTag]: c.Expr[Decoder[T]] = + materializeDecoderImpl[T](None, trueExpression, defaultDiscriminator) + + def materializeEncoder[T: c.WeakTypeTag]: c.Expr[Encoder.AsObject[T]] = + materializeEncoderImpl[T](None, defaultDiscriminator) + + def materializeCodec[T: c.WeakTypeTag]: c.Expr[Codec.AsObject[T]] = + materializeCodecImpl[T](None, trueExpression, defaultDiscriminator) def materializeDecoderWithNameTransformation[T: c.WeakTypeTag]( - nameTransformation: c.Expr[String => String] - ): c.Expr[Decoder[T]] = materializeDecoderImpl[T](Some(nameTransformation)) + nameTransformation: c.Expr[String => String], + useDefaults: c.Expr[Boolean], + discriminator: c.Expr[Discriminator] + ): c.Expr[Decoder[T]] = + materializeDecoderImpl[T]( + Some(nameTransformation), + useDefaults, + discriminator + ) def materializeEncoderWithNameTransformation[T: c.WeakTypeTag]( - nameTransformation: c.Expr[String => String] - ): c.Expr[Encoder.AsObject[T]] = materializeEncoderImpl[T](Some(nameTransformation)) + nameTransformation: c.Expr[String => String], + discriminator: c.Expr[Discriminator] + ): c.Expr[Encoder.AsObject[T]] = + materializeEncoderImpl[T](Some(nameTransformation), discriminator) def materializeCodecWithNameTransformation[T: c.WeakTypeTag]( - nameTransformation: c.Expr[String => String] - ): c.Expr[Codec.AsObject[T]] = materializeCodecImpl[T](Some(nameTransformation)) + nameTransformation: c.Expr[String => String], + useDefaults: c.Expr[Boolean], + discriminator: c.Expr[Discriminator] + ): c.Expr[Codec.AsObject[T]] = + materializeCodecImpl[T]( + Some(nameTransformation), + useDefaults, + discriminator + ) + + private[this] def materializeCodecImpl[T: c.WeakTypeTag]( + nameTransformation: Option[c.Expr[String => String]], + useDefaults: c.Expr[Boolean], + discriminator: c.Expr[Discriminator] + ): c.Expr[Codec.AsObject[T]] = { + val tpe = weakTypeOf[T] + + val subclasses = tpe.typeSymbol.asClass.knownDirectSubclasses + if (subclasses.isEmpty) { + materializeCodecCaseClassImpl[T]( + nameTransformation, + useDefaults, + discriminator + ) + } else { + materializeCodecTraitImpl[T]( + nameTransformation, + useDefaults, + discriminator, + subclasses + ) + } + } private[this] def materializeDecoderImpl[T: c.WeakTypeTag]( - nameTransformation: Option[c.Expr[String => String]] + nameTransformation: Option[c.Expr[String => String]], + useDefaults: c.Expr[Boolean], + discriminator: c.Expr[Discriminator] ): c.Expr[Decoder[T]] = { val tpe = weakTypeOf[T] - def transformName(name: String): Tree = nameTransformation.fold[Tree](q"$name")(f => q"$f($name)") + val subclasses = tpe.typeSymbol.asClass.knownDirectSubclasses + if (subclasses.isEmpty) { + materializeDecoderCaseClassImpl[T](nameTransformation, useDefaults) + } else { + materializeDecoderTraitImpl[T]( + nameTransformation, + subclasses, + discriminator + ) + } + } + + private[this] def materializeDecoderTraitImpl[T: c.WeakTypeTag]( + nameTransformation: Option[c.Expr[String => String]], + subclasses: Set[Symbol], + discriminator: c.Expr[Discriminator] + ): c.Expr[Decoder[T]] = { + val tpe = weakTypeOf[T] + + expandDiscriminator(discriminator.tree) match { + case _root_.io.circe.derivation.Discriminator.Embedded(fieldName) => + val instanceDefs: List[Tree] = subclasses.map { s => + val value = + Literal(Constant(s.asClass.name.decodedName.toString.toLowerCase())) + + cq""" $value => _root_.io.circe.Decoder[${s.asType}]""" + }.toList + + c.Expr[Decoder[T]](q""" + for { + visitorType <- _root_.io.circe.Decoder[String].prepare(_.downField($fieldName)) + value <- visitorType match { + case ..$instanceDefs + } + } yield value + + """) + case _root_.io.circe.derivation.Discriminator.TypeDiscriminator => + val instanceDefs: List[Tree] = subclasses.map { s => + val value = + Literal(Constant(s.asClass.name.decodedName.toString.toLowerCase())) + + cq""" $value => c.get(keys.head)(_root_.io.circe.Decoder[${s.asType}]).asInstanceOf[ _root_.io.circe.Decoder.Result[$tpe]]""" + }.toList + + val result = q""" + new _root_.io.circe.Decoder[$tpe] { + override def apply(c: _root_.io.circe.HCursor): _root_.io.circe.Decoder.Result[$tpe] = { + val keys=c.keys.map(_.toList).getOrElse(Nil) + if(keys.isEmpty){ + Left(_root_.io.circe.DecodingFailure("Missing type field discriminator for trait field", Nil)) + } else { + keys.head match { + case ..$instanceDefs + } + } + } + } + """ + c.Expr[Decoder[T]](result) + } + + } + + private[this] def materializeDecoderCaseClassImpl[T: c.WeakTypeTag]( + nameTransformation: Option[c.Expr[String => String]], + useDefaults: c.Expr[Boolean] + ): c.Expr[Decoder[T]] = { + val tpe = weakTypeOf[T] + + // Valid only in macro!! + val globalUseDefaults: Boolean = extractUseDefaults(useDefaults.tree) + + + def transformName(name: String): Tree = + nameTransformation.fold[Tree](q"$name")(f => q"$f($name)") productRepr(tpe).fold(fail(tpe)) { repr => if (repr.paramLists.flatten.isEmpty) { @@ -207,8 +396,12 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { val reversed = repr.paramListsWithNames.flatten.reverse - def decode(member: Member): Tree = - q"this.${repr.decoder(member.tpe).name}.tryDecode(c.downField(${transformName(member.decodedName)}))" + def decode(member: Member): Tree = { + val realFieldName = + member.keyName.getOrElse(transformName(member.decodedName)) + q"this.${repr.decoder(member.tpe).name}.tryDecode(c.downField($realFieldName))" + + } val last: Tree = q""" { @@ -227,7 +420,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { """ val result: Tree = reversed.tail.foldLeft(last) { - case (acc, (member @ Member(_, _, memberType), resultName)) => q""" + case (acc, (member @ Member(_, _, memberType, _, _, _), resultName)) => q""" { val $resName: _root_.io.circe.Decoder.Result[$memberType] = ${decode(member)} @@ -240,14 +433,27 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { """ } - val (results: List[Tree], resultNames: List[TermName]) = reversed.reverse.map { + def accumulatingDecode(member: Member): Tree = { + val realFieldName = + member.keyName.getOrElse(transformName(member.decodedName)) + if (globalUseDefaults && member.default.isDefined) { + q"""if(c.downField($realFieldName).isInstanceOf[_root_.io.circe.FailedCursor]) { + _root_.cats.data.Validated.Valid(${member.default.get}) + } else ${repr.decoder(member.tpe).name}.tryDecodeAccumulating(c.downField($realFieldName) + )""" + } else { + q"""${repr.decoder(member.tpe).name}.tryDecodeAccumulating(c.downField($realFieldName) + )""" + } + } + + val (results: List[Tree], resultNames: List[TermName]) = + reversed.reverse.map { case (member, resultName) => ( q""" val $resultName: _root_.io.circe.Decoder.AccumulatingResult[${member.tpe}] = - ${repr.decoder(member.tpe).name}.tryDecodeAccumulating( - c.downField(${transformName(member.decodedName)}) - ) + ${accumulatingDecode(member)} """, resultName ) @@ -301,7 +507,8 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { } } - private[this] def materializeEncoderImpl[T: c.WeakTypeTag]( + // we materialize case classes + private[this] def materializeEncoderCaseClassImpl[T: c.WeakTypeTag]( nameTransformation: Option[c.Expr[String => String]] ): c.Expr[Encoder.AsObject[T]] = { val tpe = weakTypeOf[T] @@ -320,10 +527,59 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { } } + // we check if we want to serialize no defaults + val hasNoDefault = repr.paramLists.flatten.exists(_.noDefaultValue) + + if (hasNoDefault) { + // we manage default serialization val fields: List[Tree] = repr.paramLists.flatten.map { - case Member(name, decodedName, tpe) => + case Member( + name, + decodedName, + tpe, + keyName, + defaultValue, + noSerializeDefault + ) => repr.encoder(tpe) match { - case Instance(_, _, instanceName) => q""" + case Instance(_, _, instanceName) => + val realName = keyName.getOrElse(transformName(decodedName)) + + if (noSerializeDefault && defaultValue.isDefined) { + q""" + if(a.$name==${defaultValue.get}) None else + Some(_root_.scala.Tuple2.apply[_root_.java.lang.String, _root_.io.circe.Json]( + $realName, $instanceName.apply(a.$name) + ))""" + } else { + q""" + Some(_root_.scala.Tuple2.apply[_root_.java.lang.String, _root_.io.circe.Json]( + $realName, $instanceName.apply(a.$name) + ))""" + + } + } + } + + val result = q""" + new _root_.io.circe.Encoder.AsObject[$tpe] { + ..$instanceDefs + + final def encodeObject(a: $tpe): _root_.io.circe.JsonObject = + _root_.io.circe.JsonObject.fromIterable($fields.flatten) + } + """ + c.Expr[Encoder.AsObject[T]](result) + + } else { + // we manage without default serialization - common case + + val fields: List[Tree] = repr.paramLists.flatten.map { + case Member(name, decodedName, tpe, keyName, _, _) => + repr.encoder(tpe) match { + case Instance(_, _, instanceName) => + val realName = keyName.getOrElse(transformName(decodedName)) + q""" _root_.scala.Tuple2.apply[_root_.java.lang.String, _root_.io.circe.Json]( ${transformName(decodedName)}, this.$instanceName.apply(a.$name) @@ -344,13 +600,239 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { ) } } + } - private[this] def materializeCodecImpl[T: c.WeakTypeTag]( - nameTransformation: Option[c.Expr[String => String]] + // we materialize trait + private[this] def materializeEncoderTraitImpl[T: c.WeakTypeTag]( + nameTransformation: Option[c.Expr[String => String]], + subclasses: Set[Symbol], + discriminator: c.Expr[Discriminator] + ): c.Expr[Encoder.AsObject[T]] = { + val tpe = weakTypeOf[T] + + val instanceDefs: List[Tree] = + expandDiscriminator(discriminator.tree) match { + case _root_.io.circe.derivation.Discriminator.Embedded(fieldName) => + subclasses.map { s => + val subTpe = s.asClass.toType + + cq"""obj : $subTpe => obj.asJsonObject.add($fieldName, ${Literal( + Constant(s.asClass.name.decodedName.toString.toLowerCase()) + )}.asJson)""" + }.toList + + case _root_.io.circe.derivation.Discriminator.TypeDiscriminator => + subclasses.map { s => + val subTpe = s.asClass.toType + + cq"""obj : $subTpe => _root_.io.circe.JsonObject(${Literal( + Constant(s.asClass.name.decodedName.toString.toLowerCase()) + )} -> + _root_.io.circe.Json.fromJsonObject(obj.asJsonObject) + )""" + }.toList + } + c.Expr[Encoder.AsObject[T]](q""" + { + import io.circe.syntax._ + new _root_.io.circe.Encoder.AsObject[$tpe] { + override def encodeObject(a: $tpe): _root_.io.circe.JsonObject = a match { + case ..$instanceDefs + } + } + } + """) + } + + private[this] def materializeEncoderImpl[T: c.WeakTypeTag]( + nameTransformation: Option[c.Expr[String => String]], + discriminator: c.Expr[Discriminator] + ): c.Expr[Encoder.AsObject[T]] = { + val tpe = weakTypeOf[T] + // we manage to work on ADT lets check the subclasses + val subclasses = tpe.typeSymbol.asClass.knownDirectSubclasses + if (subclasses.isEmpty) { + materializeEncoderCaseClassImpl[T](nameTransformation) + } else { + materializeEncoderTraitImpl[T]( + nameTransformation, + subclasses, + discriminator + ) + } + } + + // Function to extract default values from case class. + // We need to extract from companion otherwise they are no collected + def caseClassFieldsDefaults( + tpe: Type + ): ListMap[String, Option[Tree]] = + if (tpe.companion == NoType) { + ListMap() + } else { + try { + tpe.companion.member(TermName("apply")).asTerm.alternatives.find(_.isSynthetic) match { + case None => ListMap() + case Some(syntatic) => + ListMap( + syntatic.asMethod.paramLists.flatten.zipWithIndex.map { + case (field, i) => + ( + field.name.toTermName.decodedName.toString, { + val method = TermName(s"apply$$default$$${i + 1}") + tpe.companion.member(method) match { + case NoSymbol => None + case _ => Some(q"${tpe.typeSymbol.companion}.$method") + } + } + ) + }: _* + ) + } + } catch { + case ex: Throwable => + ListMap() + } + + } + + private[this] val withDiscriminatorRegex = + """.*\.withDiscriminatorName\("(.*)"\).*""".r + + private[this] def expandDiscriminator(tree: Tree): Discriminator = + tree match { + case q"io.circe.derivation.annotations.Configuration.default.discriminator" => + Discriminator.default + case other => + other.toString() match { + case withDiscriminatorRegex(name) => Discriminator.Embedded(name) + case s: String if s.contains("withTypeDiscriminator") => + Discriminator.TypeDiscriminator + case _ => +// print(showRaw(tree)) +// print(tree) + Discriminator.default + } + } + + private[this] def materializeCodecTraitImpl[T: c.WeakTypeTag]( + nameTransformation: Option[c.Expr[String => String]], + useDefaults: c.Expr[Boolean], + discriminator: c.Expr[Discriminator], + subclasses: Set[Symbol] ): c.Expr[Codec.AsObject[T]] = { val tpe = weakTypeOf[T] - def transformName(name: String): Tree = nameTransformation.fold[Tree](q"$name")(f => q"$f($name)") + def transformName(name: String): Tree = + nameTransformation.fold[Tree](q"$name")(f => q"$f($name)") + + expandDiscriminator(discriminator.tree) match { + case _root_.io.circe.derivation.Discriminator.Embedded(fieldName) => + val decoderInstanceDefs: List[Tree] = subclasses.map { s => + val value = + Literal(Constant(s.asClass.name.decodedName.toString.toLowerCase())) + + cq""" $value => _root_.io.circe.Decoder[${s.asType}].apply(c).map(_.asInstanceOf[$tpe])""" + }.toList + + val encoderInstanceDefs: List[Tree] = subclasses.map { s => + val subTpe = s.asClass.toType + + cq"""obj : $subTpe => obj.asJsonObject.add($fieldName, ${Literal( + Constant(s.asClass.name.decodedName.toString.toLowerCase()) + )}.asJson)""" + }.toList + + + val result = q""" + new _root_.io.circe.Codec.AsObject[$tpe] { + import io.circe.syntax._ + override def encodeObject(a: $tpe): _root_.io.circe.JsonObject = a match { + case ..$encoderInstanceDefs + } + + + override def apply(c: _root_.io.circe.HCursor): _root_.io.circe.Decoder.Result[$tpe] = { + def processDiscriminator(name:String):_root_.io.circe.Decoder.Result[$tpe]={ + name match { + case ..$decoderInstanceDefs + } + } + c.downField($fieldName).as[String].flatMap(t => processDiscriminator(t)) + } + } + """ + c.Expr[Codec.AsObject[T]](result) + case _root_.io.circe.derivation.Discriminator.TypeDiscriminator => + val decoderInstanceDefs: List[Tree] = subclasses.map { s => + val value = + Literal(Constant(s.asClass.name.decodedName.toString.toLowerCase())) + + cq""" $value => c.get(keys.head)(_root_.io.circe.Decoder[${s.asType}]).map(_.asInstanceOf[$tpe])""" + }.toList + + val encoderInstanceDefs: List[Tree] = subclasses.map { s => + val subTpe = s.asClass.toType + + cq"""obj : $subTpe => _root_.io.circe.JsonObject(${Literal( + Constant(s.asClass.name.decodedName.toString.toLowerCase()) + )} -> + _root_.io.circe.Json.fromJsonObject(obj.asJsonObject) + )""" + }.toList + + val result = q""" + new _root_.io.circe.Codec.AsObject[$tpe] { + import io.circe.syntax._ + override def encodeObject(a: $tpe): _root_.io.circe.JsonObject = a match { + case ..$encoderInstanceDefs + } + + override def apply(c: _root_.io.circe.HCursor): _root_.io.circe.Decoder.Result[$tpe] = { + val keys=c.keys.map(_.toList).getOrElse(Nil) + if(keys.isEmpty){ + Left(_root_.io.circe.DecodingFailure("Missing type field discriminator for trait field", Nil)) + } else { + keys.head match { + case ..$decoderInstanceDefs + } + } + } + } + """ + c.Expr[Codec.AsObject[T]](result) + } + + } + + private[this] def extractUseDefaults(useDefaults:Tree):Boolean= + useDefaults match { + case q"true " => true + case q"false " => false + case q"io.circe.derivation.annotations.Configuration.default.useDefaults " => + true + case q"Configuration.default.useDefaults " => true + case q"Configuration.default.withSnakeCaseMemberNames.useDefaults " => true + case q"io.circe.derivation.annotations.Configuration.default.withSnakeCaseMemberNames.useDefaults " => true + case q"Configuration.default.withKebabCaseMemberNames.useDefaults " => true + case q"io.circe.derivation.annotations.Configuration.default.withKebabCaseMemberNames.useDefaults " => true + case q"Configuration.decodeOnly.useDefaults " => true + case q"io.circe.derivation.annotations.Configuration.decodeOnly.useDefaults " => true + case other if other.toString().endsWith(".useDefaults") => true // hack for namespaces + } + + private[this] def materializeCodecCaseClassImpl[T: c.WeakTypeTag]( + nameTransformation: Option[c.Expr[String => String]], + useDefaults: c.Expr[Boolean], + discriminator: c.Expr[Discriminator] + ): c.Expr[Codec.AsObject[T]] = { + val tpe = weakTypeOf[T] + + // Valid only in macro!! + val globalUseDefaults: Boolean = extractUseDefaults(useDefaults.tree) + + def transformName(name: String): Tree = + nameTransformation.fold[Tree](q"$name")(f => q"$f($name)") productRepr(tpe).fold(fail(tpe)) { repr => if (repr.paramLists.flatten.isEmpty) { @@ -383,15 +865,35 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { } } + + + // we manage default serialization val fields: List[Tree] = repr.paramLists.flatten.map { - case Member(name, decodedName, tpe) => + case Member( + name, + decodedName, + tpe, + keyName, + defaultValue, + noSerializeDefault + ) => repr.encoder(tpe) match { - case Instance(_, _, instanceName) => q""" - _root_.scala.Tuple2.apply[_root_.java.lang.String, _root_.io.circe.Json]( - ${transformName(decodedName)}, - this.$instanceName.apply(a.$name) - ) - """ + case Instance(_, _, instanceName) => + val realName = keyName.getOrElse(transformName(decodedName)) + + if (noSerializeDefault && defaultValue.isDefined) { + q""" + if(a.$name==${defaultValue.get}) None else + Some(_root_.scala.Tuple2.apply[_root_.java.lang.String, _root_.io.circe.Json]( + $realName, $instanceName.apply(a.$name) + ))""" + } else { + q""" + Some(_root_.scala.Tuple2.apply[_root_.java.lang.String, _root_.io.circe.Json]( + $realName, $instanceName.apply(a.$name) + ))""" + + } } } @@ -408,8 +910,20 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { val reversed = repr.paramListsWithNames.flatten.reverse - def decode(member: Member): Tree = - q"this.${repr.decoder(member.tpe).name}.tryDecode(c.downField(${transformName(member.decodedName)}))" + def decode(member: Member): Tree = { + val realFieldName = + member.keyName.getOrElse(transformName(member.decodedName)) + if (globalUseDefaults && member.default.isDefined) { + q"""if(c.downField($realFieldName).isInstanceOf[_root_.io.circe.FailedCursor]) { + Right(${member.default.get}) + } else ${repr.decoder(member.tpe).name}.tryDecode(c.downField($realFieldName) + )""" + } else { + q"""${repr.decoder(member.tpe).name}.tryDecode(c.downField($realFieldName) + )""" + } + //q"this.${ repr.decoder(member.tpe).name }.tryDecode(c.downField($realFieldName))" + } val last: Tree = q""" { @@ -428,7 +942,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { """ val result: Tree = reversed.tail.foldLeft(last) { - case (acc, (member @ Member(_, _, memberType), resultName)) => q""" + case (acc, (member @ Member(_, _, memberType, _, _, _), resultName)) => q""" { val $resName: _root_.io.circe.Decoder.Result[$memberType] = ${decode(member)} @@ -441,14 +955,27 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { """ } - val (results: List[Tree], resultNames: List[TermName]) = reversed.reverse.map { + def accumulatingDecode(member: Member): Tree = { + val realFieldName = + member.keyName.getOrElse(transformName(member.decodedName)) + if (globalUseDefaults && member.default.isDefined) { + q"""if(c.downField($realFieldName).isInstanceOf[_root_.io.circe.FailedCursor]) { + _root_.cats.data.Validated.Valid(${member.default.get}) + } else ${repr.decoder(member.tpe).name}.tryDecodeAccumulating(c.downField($realFieldName) + )""" + } else { + q"""${repr.decoder(member.tpe).name}.tryDecodeAccumulating(c.downField($realFieldName) + )""" + } + } + + val (results: List[Tree], resultNames: List[TermName]) = + reversed.reverse.map { case (member, resultName) => ( q""" val $resultName: _root_.io.circe.Decoder.AccumulatingResult[${member.tpe}] = - ${repr.decoder(member.tpe).name}.tryDecodeAccumulating( - c.downField(${transformName(member.decodedName)}) - ) + ${accumulatingDecode(member)} """, resultName ) @@ -485,7 +1012,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { ..$decoderInstanceDefs final def encodeObject(a: $tpe): _root_.io.circe.JsonObject = - _root_.io.circe.JsonObject.fromIterable($fields) + _root_.io.circe.JsonObject.fromIterable($fields.flatten) final def apply(c: _root_.io.circe.HCursor): _root_.io.circe.Decoder.Result[$tpe] = $result diff --git a/modules/derivation/shared/src/main/scala/io/circe/derivation/Discriminator.scala b/modules/derivation/shared/src/main/scala/io/circe/derivation/Discriminator.scala new file mode 100644 index 00000000..66573890 --- /dev/null +++ b/modules/derivation/shared/src/main/scala/io/circe/derivation/Discriminator.scala @@ -0,0 +1,12 @@ +package io.circe.derivation + +//Used to manage different ways of ADT serialization + +sealed trait Discriminator + +object Discriminator { + final case class Embedded(fieldName: String) extends Discriminator + final case object TypeDiscriminator extends Discriminator + + lazy val default = Embedded("type") +} diff --git a/modules/derivation/shared/src/main/scala/io/circe/derivation/package.scala b/modules/derivation/shared/src/main/scala/io/circe/derivation/package.scala index edc1b597..517ab224 100644 --- a/modules/derivation/shared/src/main/scala/io/circe/derivation/package.scala +++ b/modules/derivation/shared/src/main/scala/io/circe/derivation/package.scala @@ -7,10 +7,23 @@ package object derivation { final def deriveEncoder[A]: Encoder.AsObject[A] = macro DerivationMacros.materializeEncoder[A] final def deriveCodec[A]: Codec.AsObject[A] = macro DerivationMacros.materializeCodec[A] - final def deriveDecoder[A](nameTransformation: String => String): Decoder[A] = + final def deriveDecoder[A]( + nameTransformation: String => String, + useDefaults: Boolean, + discriminator: Discriminator + ): Decoder[A] = macro DerivationMacros.materializeDecoderWithNameTransformation[A] - final def deriveEncoder[A](nameTransformation: String => String): Encoder.AsObject[A] = + + final def deriveEncoder[A]( + nameTransformation: String => String, + discriminator: Discriminator + ): Encoder.AsObject[A] = macro DerivationMacros.materializeEncoderWithNameTransformation[A] - final def deriveCodec[A](nameTransformation: String => String): Codec.AsObject[A] = + + final def deriveCodec[A]( + nameTransformation: String => String, + useDefaults: Boolean, + discriminator: Discriminator + ): Codec.AsObject[A] = macro DerivationMacros.materializeCodecWithNameTransformation[A] } diff --git a/modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationExample.scala b/modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationExample.scala index eca74a14..3dc5e6e5 100644 --- a/modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationExample.scala +++ b/modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationExample.scala @@ -19,9 +19,9 @@ object NameTransformationExample { implicit val eqUser: Eq[User] = Eq.fromUniversalEquals - implicit val encodeUser: Encoder[User] = deriveEncoder(renaming.snakeCase) - implicit val decodeUser: Decoder[User] = deriveDecoder(renaming.snakeCase) - val codecForUser: Codec[User] = deriveCodec(renaming.snakeCase) + implicit val encodeUser: Encoder[User] = deriveEncoder(renaming.snakeCase, Discriminator.default) + implicit val decodeUser: Decoder[User] = deriveDecoder(renaming.snakeCase, true, Discriminator.default) + val codecForUser: Codec[User] = deriveCodec(renaming.snakeCase, true, Discriminator.default) } case class Role(title: String) @@ -30,8 +30,8 @@ object NameTransformationExample { implicit val arbitraryRole: Arbitrary[Role] = Arbitrary(Arbitrary.arbitrary[String].map(Role(_))) implicit val eqRole: Eq[Role] = Eq.fromUniversalEquals - implicit val encodeRole: Encoder[Role] = deriveEncoder(_.toUpperCase) - implicit val decodeRole: Decoder[Role] = deriveDecoder(_.toUpperCase) + implicit val encodeRole: Encoder[Role] = deriveEncoder(_.toUpperCase, Discriminator.default) + implicit val decodeRole: Decoder[Role] = deriveDecoder(_.toUpperCase, true, Discriminator.default) } case class Address(number: Int, street: String, city: String) @@ -47,8 +47,11 @@ object NameTransformationExample { implicit val eqAddress: Eq[Address] = Eq.fromUniversalEquals - implicit val encodeAddress: Encoder[Address] = deriveEncoder(renaming.replaceWith("number" -> "#")) - implicit val decodeAddress: Decoder[Address] = deriveDecoder(renaming.replaceWith("number" -> "#")) + implicit val encodeAddress: Encoder[Address] = deriveEncoder(renaming.replaceWith("number" -> "#"), + Discriminator.default) + implicit val decodeAddress: Decoder[Address] = deriveDecoder(renaming.replaceWith("number" -> "#"), + true, + Discriminator.default) } case class Abc(a: String, b: String, c: String) @@ -64,7 +67,7 @@ object NameTransformationExample { implicit val eqAbc: Eq[Abc] = Eq.fromUniversalEquals - implicit val encodeAbc: Encoder[Abc] = deriveEncoder(_ => "x") - implicit val decodeAbc: Decoder[Abc] = deriveDecoder(_ => "x") + implicit val encodeAbc: Encoder[Abc] = deriveEncoder(_ => "x", Discriminator.default) + implicit val decodeAbc: Decoder[Abc] = deriveDecoder(_ => "x", true, Discriminator.default) } } diff --git a/modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationSuite.scala b/modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationSuite.scala index 98ea5350..073e72a9 100644 --- a/modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationSuite.scala +++ b/modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationSuite.scala @@ -7,23 +7,28 @@ import io.circe.syntax._ import io.circe.testing.CodecTests object NameTransformationSuiteCodecs extends Serializable { - implicit val decodeFoo: Decoder[Foo] = deriveDecoder(renaming.snakeCase) - implicit val encodeFoo: Encoder.AsObject[Foo] = deriveEncoder(renaming.snakeCase) - val codecForFoo: Codec.AsObject[Foo] = deriveCodec(renaming.snakeCase) + implicit val decodeFoo: Decoder[Foo] = deriveDecoder(renaming.snakeCase, true, Discriminator.default) + implicit val encodeFoo: Encoder.AsObject[Foo] = deriveEncoder(renaming.snakeCase, Discriminator.default) + val codecForFoo: Codec.AsObject[Foo] = deriveCodec(renaming.snakeCase, true, Discriminator.default) - implicit val decodeBar: Decoder[Bar] = deriveDecoder(renaming.snakeCase) - implicit val encodeBar: Encoder.AsObject[Bar] = deriveEncoder(renaming.snakeCase) - val codecForBar: Codec.AsObject[Bar] = deriveCodec(renaming.snakeCase) + implicit val decodeBar: Decoder[Bar] = deriveDecoder(renaming.snakeCase, true, Discriminator.default) + implicit val encodeBar: Encoder.AsObject[Bar] = deriveEncoder(renaming.snakeCase, Discriminator.default) + val codecForBar: Codec.AsObject[Bar] = deriveCodec(renaming.snakeCase, true, Discriminator.default) - implicit val decodeBaz: Decoder[Baz] = deriveDecoder(renaming.snakeCase) - implicit val encodeBaz: Encoder.AsObject[Baz] = deriveEncoder(renaming.snakeCase) - val codecForBaz: Codec.AsObject[Baz] = deriveCodec(renaming.snakeCase) + implicit val decodeBaz: Decoder[Baz] = deriveDecoder(renaming.snakeCase, true, Discriminator.default) + implicit val encodeBaz: Encoder.AsObject[Baz] = deriveEncoder(renaming.snakeCase, Discriminator.default) + val codecForBaz: Codec.AsObject[Baz] = deriveCodec(renaming.snakeCase, true, Discriminator.default) - implicit def decodeQux[A: Decoder]: Decoder[Qux[A]] = deriveDecoder(renaming.replaceWith("aa" -> "1", "bb" -> "2")) + implicit def decodeQux[A: Decoder]: Decoder[Qux[A]] = deriveDecoder(renaming.replaceWith("aa" -> "1", "bb" -> "2"), + true, + Discriminator.default) implicit def encodeQux[A: Encoder]: Encoder.AsObject[Qux[A]] = - deriveEncoder(renaming.replaceWith("aa" -> "1", "bb" -> "2")) + deriveEncoder(renaming.replaceWith("aa" -> "1", "bb" -> "2"), + Discriminator.default) def codecForQux[A: Decoder: Encoder]: Codec.AsObject[Qux[A]] = deriveCodec( - renaming.replaceWith("aa" -> "1", "bb" -> "2") + renaming.replaceWith("aa" -> "1", "bb" -> "2"), + true, + Discriminator.default ) } From 600f84a77d778baee06478483e68959339b48456 Mon Sep 17 00:00:00 2001 From: Alberto Paro Date: Mon, 12 Aug 2019 14:17:53 +0200 Subject: [PATCH 2/8] All test passed --- .../scala/io/circe/derivation/annotations/JsonCodec.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala b/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala index e4e6993b..7056874c 100644 --- a/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala +++ b/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala @@ -141,13 +141,13 @@ private[derivation] final class GenericJsonCodecMacros(val c: blackbox.Context) val Type = tq"$tpname[..$tparamNames]" ( q"""implicit def $decoderName[..$tparams](implicit ..$decodeParams): $DecoderClass[$Type] = - _root_.io.circe.derivation.deriveDecoder[$Type]($cfgNameTransformation)""", + _root_.io.circe.derivation.deriveDecoder[$Type]($cfgNameTransformation, $cfgUseDefaults, $cfgDiscriminator)""", q"""implicit def $encoderName[..$tparams](implicit ..$encodeParams): $AsObjectEncoderClass[$Type] = - _root_.io.circe.derivation.deriveEncoder[$Type]($cfgNameTransformation)""", + _root_.io.circe.derivation.deriveEncoder[$Type]($cfgNameTransformation, $cfgDiscriminator)""", q"""implicit def $codecName[..$tparams](implicit ..${decodeParams ++ encodeParams} ): $AsObjectCodecClass[$Type] = - _root_.io.circe.derivation.deriveCodec[$Type]($cfgNameTransformation)""" + _root_.io.circe.derivation.deriveCodec[$Type]($cfgNameTransformation, $cfgUseDefaults, $cfgDiscriminator)""" ) } codecType match { From b7e2fb8743a73a85c1c70e9bb2ec4d6665af624e Mon Sep 17 00:00:00 2001 From: Alberto Paro Date: Mon, 12 Aug 2019 14:22:07 +0200 Subject: [PATCH 3/8] Added .metals and .bloop to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d0fbd3da..be3b3cc0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ target/ .project .classpath tmp/ +.bloop +.metals \ No newline at end of file From af80a4e51b1279640ee103963b40d28e39a5d40c Mon Sep 17 00:00:00 2001 From: Alberto Paro Date: Mon, 12 Aug 2019 14:29:22 +0200 Subject: [PATCH 4/8] Updated circe to 0.12.0-RC2 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index b00bbaac..b25bec0b 100644 --- a/build.sbt +++ b/build.sbt @@ -17,9 +17,9 @@ val compilerOptions = Seq( ) val catsVersion = "2.0.0-RC1" -val circeVersion = "0.12.0-RC1" +val circeVersion = "0.12.0-RC2" val paradiseVersion = "2.1.1" -val previousCirceDerivationVersion = "0.12.0-M4" +val previousCirceDerivationVersion = "0.12.0-RC1" val scalaCheckVersion = "1.14.0" val scalaJavaTimeVersion = "2.0.0-RC3" From a117913b2160b7ef365b705ae18ccf999f2c569a Mon Sep 17 00:00:00 2001 From: Alberto Paro Date: Mon, 12 Aug 2019 15:16:44 +0200 Subject: [PATCH 5/8] Removed comments --- .../src/main/scala/io/circe/derivation/DerivationMacros.scala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala b/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala index 5bb83809..13be8ced 100644 --- a/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala +++ b/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala @@ -358,10 +358,8 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { ): c.Expr[Decoder[T]] = { val tpe = weakTypeOf[T] - // Valid only in macro!! val globalUseDefaults: Boolean = extractUseDefaults(useDefaults.tree) - def transformName(name: String): Tree = nameTransformation.fold[Tree](q"$name")(f => q"$f($name)") @@ -709,8 +707,6 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { case s: String if s.contains("withTypeDiscriminator") => Discriminator.TypeDiscriminator case _ => -// print(showRaw(tree)) -// print(tree) Discriminator.default } } From c66050176335b6424080b22021afb9d5c833b440 Mon Sep 17 00:00:00 2001 From: Alberto Paro Date: Mon, 12 Aug 2019 15:17:54 +0200 Subject: [PATCH 6/8] Applied sbt scalafmt --- .../derivation/annotations/JsonKey.scala | 2 +- .../circe/derivation/DerivationMacros.scala | 73 +++++++++---------- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonKey.scala b/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonKey.scala index d704af2c..3fd1aa3e 100644 --- a/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonKey.scala +++ b/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonKey.scala @@ -4,4 +4,4 @@ import scala.annotation.StaticAnnotation final case class JsonKey(value: String) extends StaticAnnotation -final case class JsonNoDefault() extends StaticAnnotation \ No newline at end of file +final case class JsonNoDefault() extends StaticAnnotation diff --git a/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala b/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala index 13be8ced..93e8d61d 100644 --- a/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala +++ b/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala @@ -74,7 +74,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { case "JsonNoDefault" => noDefault = true case _ => - // we skip other annotations + // we skip other annotations } case extra => extra.toString().split('.').last match { @@ -157,15 +157,15 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { private[this] def membersFromCompanionApply(tpe: Type): Option[ProductRepr] = tpe.companion.decl(applyName) match { case NoSymbol => None case s => - val defaults = caseClassFieldsDefaults(tpe) + val defaults = caseClassFieldsDefaults(tpe) s.alternatives.collect { case m: MethodSymbol => m.paramLists }.sortBy(-_.map(_.size).sum).headOption.map { applyParams => - //We use zipwithIndex for gathering default field value if available - ProductReprWithApply( - tpe, - applyParams.map(_.zipWithIndex.map(Member.fromSymbol(tpe, defaults))) - ) + //We use zipwithIndex for gathering default field value if available + ProductReprWithApply( + tpe, + applyParams.map(_.zipWithIndex.map(Member.fromSymbol(tpe, defaults))) + ) } } @@ -447,15 +447,15 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { val (results: List[Tree], resultNames: List[TermName]) = reversed.reverse.map { - case (member, resultName) => - ( - q""" + case (member, resultName) => + ( + q""" val $resultName: _root_.io.circe.Decoder.AccumulatingResult[${member.tpe}] = ${accumulatingDecode(member)} """, - resultName - ) - }.unzip + resultName + ) + }.unzip val resultErrors: List[Tree] = resultNames.map { resultName => q"errors($resultName)" @@ -530,7 +530,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { if (hasNoDefault) { // we manage default serialization - val fields: List[Tree] = repr.paramLists.flatten.map { + val fields: List[Tree] = repr.paramLists.flatten.map { case Member( name, decodedName, @@ -539,7 +539,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { defaultValue, noSerializeDefault ) => - repr.encoder(tpe) match { + repr.encoder(tpe) match { case Instance(_, _, instanceName) => val realName = keyName.getOrElse(transformName(decodedName)) @@ -583,11 +583,11 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { this.$instanceName.apply(a.$name) ) """ - } - } + } + } - c.Expr[Encoder.AsObject[T]]( - q""" + c.Expr[Encoder.AsObject[T]]( + q""" new _root_.io.circe.Encoder.AsObject[$tpe] { ..$instanceDefs @@ -595,10 +595,10 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { _root_.io.circe.JsonObject.fromIterable($fields) } """ - ) + ) + } } } - } // we materialize trait private[this] def materializeEncoderTraitImpl[T: c.WeakTypeTag]( @@ -739,7 +739,6 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { )}.asJson)""" }.toList - val result = q""" new _root_.io.circe.Codec.AsObject[$tpe] { import io.circe.syntax._ @@ -801,20 +800,20 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { } - private[this] def extractUseDefaults(useDefaults:Tree):Boolean= + private[this] def extractUseDefaults(useDefaults: Tree): Boolean = useDefaults match { - case q"true " => true + case q"true " => true case q"false " => false case q"io.circe.derivation.annotations.Configuration.default.useDefaults " => true - case q"Configuration.default.useDefaults " => true - case q"Configuration.default.withSnakeCaseMemberNames.useDefaults " => true + case q"Configuration.default.useDefaults " => true + case q"Configuration.default.withSnakeCaseMemberNames.useDefaults " => true case q"io.circe.derivation.annotations.Configuration.default.withSnakeCaseMemberNames.useDefaults " => true - case q"Configuration.default.withKebabCaseMemberNames.useDefaults " => true + case q"Configuration.default.withKebabCaseMemberNames.useDefaults " => true case q"io.circe.derivation.annotations.Configuration.default.withKebabCaseMemberNames.useDefaults " => true - case q"Configuration.decodeOnly.useDefaults " => true - case q"io.circe.derivation.annotations.Configuration.decodeOnly.useDefaults " => true - case other if other.toString().endsWith(".useDefaults") => true // hack for namespaces + case q"Configuration.decodeOnly.useDefaults " => true + case q"io.circe.derivation.annotations.Configuration.decodeOnly.useDefaults " => true + case other if other.toString().endsWith(".useDefaults") => true // hack for namespaces } private[this] def materializeCodecCaseClassImpl[T: c.WeakTypeTag]( @@ -861,8 +860,6 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { } } - - // we manage default serialization val fields: List[Tree] = repr.paramLists.flatten.map { case Member( @@ -967,15 +964,15 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { val (results: List[Tree], resultNames: List[TermName]) = reversed.reverse.map { - case (member, resultName) => - ( - q""" + case (member, resultName) => + ( + q""" val $resultName: _root_.io.circe.Decoder.AccumulatingResult[${member.tpe}] = ${accumulatingDecode(member)} """, - resultName - ) - }.unzip + resultName + ) + }.unzip val resultErrors: List[Tree] = resultNames.map { resultName => q"errors($resultName)" From 9f378266894d2e1d5235aa978af3721004d8bf71 Mon Sep 17 00:00:00 2001 From: Alberto Paro Date: Mon, 12 Aug 2019 15:22:44 +0200 Subject: [PATCH 7/8] Removed unused comment --- .../src/main/scala/io/circe/derivation/DerivationMacros.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala b/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala index 93e8d61d..25bcf8f8 100644 --- a/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala +++ b/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala @@ -915,7 +915,6 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { q"""${repr.decoder(member.tpe).name}.tryDecode(c.downField($realFieldName) )""" } - //q"this.${ repr.decoder(member.tpe).name }.tryDecode(c.downField($realFieldName))" } val last: Tree = q""" From 4d55eccd5408ae71618a163443386d2a44795e4c Mon Sep 17 00:00:00 2001 From: Alberto Paro Date: Mon, 12 Aug 2019 16:00:34 +0200 Subject: [PATCH 8/8] Unification names with circe.generic.auto: nameTransformation => transformMemberNames --- .../derivation/annotations/JsonCodec.scala | 14 ++--- .../circe/derivation/DerivationMacros.scala | 56 +++++++++---------- .../scala/io/circe/derivation/package.scala | 12 ++-- ...cala => TransformMemberNamesExample.scala} | 2 +- ....scala => TransformMemberNamesSuite.scala} | 8 +-- 5 files changed, 46 insertions(+), 46 deletions(-) rename modules/derivation/shared/src/test/scala/io/circe/derivation/{NameTransformationExample.scala => TransformMemberNamesExample.scala} (98%) rename modules/derivation/shared/src/test/scala/io/circe/derivation/{NameTransformationSuite.scala => TransformMemberNamesSuite.scala} (95%) diff --git a/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala b/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala index 7056874c..fca6e0a4 100644 --- a/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala +++ b/modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala @@ -101,7 +101,7 @@ private[derivation] final class GenericJsonCodecMacros(val c: blackbox.Context) JsonCodecType.Both } - private[this] val cfgNameTransformation = + private[this] val cfgTransformMemberNames = q"$config.transformMemberNames" private[this] val cfgUseDefaults = q"$config.useDefaults" @@ -121,11 +121,11 @@ private[derivation] final class GenericJsonCodecMacros(val c: blackbox.Context) val Type = tpname ( q"""implicit val $decoderName: $DecoderClass[$Type] = - _root_.io.circe.derivation.deriveDecoder[$Type]($cfgNameTransformation, $cfgUseDefaults, $cfgDiscriminator)""", + _root_.io.circe.derivation.deriveDecoder[$Type]($cfgTransformMemberNames, $cfgUseDefaults, $cfgDiscriminator)""", q"""implicit val $encoderName: $AsObjectEncoderClass[$Type] = - _root_.io.circe.derivation.deriveEncoder[$Type]($cfgNameTransformation, $cfgDiscriminator)""", + _root_.io.circe.derivation.deriveEncoder[$Type]($cfgTransformMemberNames, $cfgDiscriminator)""", q"""implicit val $codecName: $AsObjectCodecClass[$Type] = - _root_.io.circe.derivation.deriveCodec[$Type]($cfgNameTransformation, $cfgUseDefaults, $cfgDiscriminator)""" + _root_.io.circe.derivation.deriveCodec[$Type]($cfgTransformMemberNames, $cfgUseDefaults, $cfgDiscriminator)""" ) } else { val tparamNames = tparams.map(_.name) @@ -141,13 +141,13 @@ private[derivation] final class GenericJsonCodecMacros(val c: blackbox.Context) val Type = tq"$tpname[..$tparamNames]" ( q"""implicit def $decoderName[..$tparams](implicit ..$decodeParams): $DecoderClass[$Type] = - _root_.io.circe.derivation.deriveDecoder[$Type]($cfgNameTransformation, $cfgUseDefaults, $cfgDiscriminator)""", + _root_.io.circe.derivation.deriveDecoder[$Type]($cfgTransformMemberNames, $cfgUseDefaults, $cfgDiscriminator)""", q"""implicit def $encoderName[..$tparams](implicit ..$encodeParams): $AsObjectEncoderClass[$Type] = - _root_.io.circe.derivation.deriveEncoder[$Type]($cfgNameTransformation, $cfgDiscriminator)""", + _root_.io.circe.derivation.deriveEncoder[$Type]($cfgTransformMemberNames, $cfgDiscriminator)""", q"""implicit def $codecName[..$tparams](implicit ..${decodeParams ++ encodeParams} ): $AsObjectCodecClass[$Type] = - _root_.io.circe.derivation.deriveCodec[$Type]($cfgNameTransformation, $cfgUseDefaults, $cfgDiscriminator)""" + _root_.io.circe.derivation.deriveCodec[$Type]($cfgTransformMemberNames, $cfgUseDefaults, $cfgDiscriminator)""" ) } codecType match { diff --git a/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala b/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala index 25bcf8f8..bc85cb87 100644 --- a/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala +++ b/modules/derivation/shared/src/main/scala/io/circe/derivation/DerivationMacros.scala @@ -229,36 +229,36 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { def materializeCodec[T: c.WeakTypeTag]: c.Expr[Codec.AsObject[T]] = materializeCodecImpl[T](None, trueExpression, defaultDiscriminator) - def materializeDecoderWithNameTransformation[T: c.WeakTypeTag]( - nameTransformation: c.Expr[String => String], + def materializeDecoderWithTransformMemberNames[T: c.WeakTypeTag]( + transformMemberNames: c.Expr[String => String], useDefaults: c.Expr[Boolean], discriminator: c.Expr[Discriminator] ): c.Expr[Decoder[T]] = materializeDecoderImpl[T]( - Some(nameTransformation), + Some(transformMemberNames), useDefaults, discriminator ) - def materializeEncoderWithNameTransformation[T: c.WeakTypeTag]( - nameTransformation: c.Expr[String => String], + def materializeEncoderWithTransformMemberNames[T: c.WeakTypeTag]( + transformMemberNames: c.Expr[String => String], discriminator: c.Expr[Discriminator] ): c.Expr[Encoder.AsObject[T]] = - materializeEncoderImpl[T](Some(nameTransformation), discriminator) + materializeEncoderImpl[T](Some(transformMemberNames), discriminator) - def materializeCodecWithNameTransformation[T: c.WeakTypeTag]( - nameTransformation: c.Expr[String => String], + def materializeCodecWithTransformMemberNames[T: c.WeakTypeTag]( + transformMemberNames: c.Expr[String => String], useDefaults: c.Expr[Boolean], discriminator: c.Expr[Discriminator] ): c.Expr[Codec.AsObject[T]] = materializeCodecImpl[T]( - Some(nameTransformation), + Some(transformMemberNames), useDefaults, discriminator ) private[this] def materializeCodecImpl[T: c.WeakTypeTag]( - nameTransformation: Option[c.Expr[String => String]], + transformMemberNames: Option[c.Expr[String => String]], useDefaults: c.Expr[Boolean], discriminator: c.Expr[Discriminator] ): c.Expr[Codec.AsObject[T]] = { @@ -267,13 +267,13 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { val subclasses = tpe.typeSymbol.asClass.knownDirectSubclasses if (subclasses.isEmpty) { materializeCodecCaseClassImpl[T]( - nameTransformation, + transformMemberNames, useDefaults, discriminator ) } else { materializeCodecTraitImpl[T]( - nameTransformation, + transformMemberNames, useDefaults, discriminator, subclasses @@ -282,7 +282,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { } private[this] def materializeDecoderImpl[T: c.WeakTypeTag]( - nameTransformation: Option[c.Expr[String => String]], + transformMemberNames: Option[c.Expr[String => String]], useDefaults: c.Expr[Boolean], discriminator: c.Expr[Discriminator] ): c.Expr[Decoder[T]] = { @@ -290,10 +290,10 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { val subclasses = tpe.typeSymbol.asClass.knownDirectSubclasses if (subclasses.isEmpty) { - materializeDecoderCaseClassImpl[T](nameTransformation, useDefaults) + materializeDecoderCaseClassImpl[T](transformMemberNames, useDefaults) } else { materializeDecoderTraitImpl[T]( - nameTransformation, + transformMemberNames, subclasses, discriminator ) @@ -301,7 +301,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { } private[this] def materializeDecoderTraitImpl[T: c.WeakTypeTag]( - nameTransformation: Option[c.Expr[String => String]], + transformMemberNames: Option[c.Expr[String => String]], subclasses: Set[Symbol], discriminator: c.Expr[Discriminator] ): c.Expr[Decoder[T]] = { @@ -353,7 +353,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { } private[this] def materializeDecoderCaseClassImpl[T: c.WeakTypeTag]( - nameTransformation: Option[c.Expr[String => String]], + transformMemberNames: Option[c.Expr[String => String]], useDefaults: c.Expr[Boolean] ): c.Expr[Decoder[T]] = { val tpe = weakTypeOf[T] @@ -361,7 +361,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { val globalUseDefaults: Boolean = extractUseDefaults(useDefaults.tree) def transformName(name: String): Tree = - nameTransformation.fold[Tree](q"$name")(f => q"$f($name)") + transformMemberNames.fold[Tree](q"$name")(f => q"$f($name)") productRepr(tpe).fold(fail(tpe)) { repr => if (repr.paramLists.flatten.isEmpty) { @@ -507,11 +507,11 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { // we materialize case classes private[this] def materializeEncoderCaseClassImpl[T: c.WeakTypeTag]( - nameTransformation: Option[c.Expr[String => String]] + transformMemberNames: Option[c.Expr[String => String]] ): c.Expr[Encoder.AsObject[T]] = { val tpe = weakTypeOf[T] - def transformName(name: String): Tree = nameTransformation.fold[Tree](q"$name")(f => q"$f($name)") + def transformName(name: String): Tree = transformMemberNames.fold[Tree](q"$name")(f => q"$f($name)") productRepr(tpe).fold(fail(tpe)) { repr => val instanceDefs: List[Tree] = repr.instances.map(_.encoder).map { @@ -602,7 +602,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { // we materialize trait private[this] def materializeEncoderTraitImpl[T: c.WeakTypeTag]( - nameTransformation: Option[c.Expr[String => String]], + transformMemberNames: Option[c.Expr[String => String]], subclasses: Set[Symbol], discriminator: c.Expr[Discriminator] ): c.Expr[Encoder.AsObject[T]] = { @@ -643,17 +643,17 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { } private[this] def materializeEncoderImpl[T: c.WeakTypeTag]( - nameTransformation: Option[c.Expr[String => String]], + transformMemberNames: Option[c.Expr[String => String]], discriminator: c.Expr[Discriminator] ): c.Expr[Encoder.AsObject[T]] = { val tpe = weakTypeOf[T] // we manage to work on ADT lets check the subclasses val subclasses = tpe.typeSymbol.asClass.knownDirectSubclasses if (subclasses.isEmpty) { - materializeEncoderCaseClassImpl[T](nameTransformation) + materializeEncoderCaseClassImpl[T](transformMemberNames) } else { materializeEncoderTraitImpl[T]( - nameTransformation, + transformMemberNames, subclasses, discriminator ) @@ -712,7 +712,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { } private[this] def materializeCodecTraitImpl[T: c.WeakTypeTag]( - nameTransformation: Option[c.Expr[String => String]], + transformMemberNames: Option[c.Expr[String => String]], useDefaults: c.Expr[Boolean], discriminator: c.Expr[Discriminator], subclasses: Set[Symbol] @@ -720,7 +720,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { val tpe = weakTypeOf[T] def transformName(name: String): Tree = - nameTransformation.fold[Tree](q"$name")(f => q"$f($name)") + transformMemberNames.fold[Tree](q"$name")(f => q"$f($name)") expandDiscriminator(discriminator.tree) match { case _root_.io.circe.derivation.Discriminator.Embedded(fieldName) => @@ -817,7 +817,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { } private[this] def materializeCodecCaseClassImpl[T: c.WeakTypeTag]( - nameTransformation: Option[c.Expr[String => String]], + transformMemberNames: Option[c.Expr[String => String]], useDefaults: c.Expr[Boolean], discriminator: c.Expr[Discriminator] ): c.Expr[Codec.AsObject[T]] = { @@ -827,7 +827,7 @@ class DerivationMacros(val c: blackbox.Context) extends ScalaVersionCompat { val globalUseDefaults: Boolean = extractUseDefaults(useDefaults.tree) def transformName(name: String): Tree = - nameTransformation.fold[Tree](q"$name")(f => q"$f($name)") + transformMemberNames.fold[Tree](q"$name")(f => q"$f($name)") productRepr(tpe).fold(fail(tpe)) { repr => if (repr.paramLists.flatten.isEmpty) { diff --git a/modules/derivation/shared/src/main/scala/io/circe/derivation/package.scala b/modules/derivation/shared/src/main/scala/io/circe/derivation/package.scala index 517ab224..ef264085 100644 --- a/modules/derivation/shared/src/main/scala/io/circe/derivation/package.scala +++ b/modules/derivation/shared/src/main/scala/io/circe/derivation/package.scala @@ -8,22 +8,22 @@ package object derivation { final def deriveCodec[A]: Codec.AsObject[A] = macro DerivationMacros.materializeCodec[A] final def deriveDecoder[A]( - nameTransformation: String => String, + transformMemberNames: String => String, useDefaults: Boolean, discriminator: Discriminator ): Decoder[A] = - macro DerivationMacros.materializeDecoderWithNameTransformation[A] + macro DerivationMacros.materializeDecoderWithTransformMemberNames[A] final def deriveEncoder[A]( - nameTransformation: String => String, + transformMemberNames: String => String, discriminator: Discriminator ): Encoder.AsObject[A] = - macro DerivationMacros.materializeEncoderWithNameTransformation[A] + macro DerivationMacros.materializeEncoderWithTransformMemberNames[A] final def deriveCodec[A]( - nameTransformation: String => String, + transformMemberNames: String => String, useDefaults: Boolean, discriminator: Discriminator ): Codec.AsObject[A] = - macro DerivationMacros.materializeCodecWithNameTransformation[A] + macro DerivationMacros.materializeCodecWithTransformMemberNames[A] } diff --git a/modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationExample.scala b/modules/derivation/shared/src/test/scala/io/circe/derivation/TransformMemberNamesExample.scala similarity index 98% rename from modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationExample.scala rename to modules/derivation/shared/src/test/scala/io/circe/derivation/TransformMemberNamesExample.scala index 3dc5e6e5..2e22eb18 100644 --- a/modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationExample.scala +++ b/modules/derivation/shared/src/test/scala/io/circe/derivation/TransformMemberNamesExample.scala @@ -4,7 +4,7 @@ import cats.kernel.Eq import io.circe.{ Codec, Decoder, Encoder } import org.scalacheck.Arbitrary -object NameTransformationExample { +object TransformMemberNamesExample { case class User(firstName: String, lastName: String, role: Role, address: Address) object User { diff --git a/modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationSuite.scala b/modules/derivation/shared/src/test/scala/io/circe/derivation/TransformMemberNamesSuite.scala similarity index 95% rename from modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationSuite.scala rename to modules/derivation/shared/src/test/scala/io/circe/derivation/TransformMemberNamesSuite.scala index 073e72a9..d38f6acc 100644 --- a/modules/derivation/shared/src/test/scala/io/circe/derivation/NameTransformationSuite.scala +++ b/modules/derivation/shared/src/test/scala/io/circe/derivation/TransformMemberNamesSuite.scala @@ -6,7 +6,7 @@ import io.circe.parser.decode import io.circe.syntax._ import io.circe.testing.CodecTests -object NameTransformationSuiteCodecs extends Serializable { +object TransformMemberNamesSuiteCodecs extends Serializable { implicit val decodeFoo: Decoder[Foo] = deriveDecoder(renaming.snakeCase, true, Discriminator.default) implicit val encodeFoo: Encoder.AsObject[Foo] = deriveEncoder(renaming.snakeCase, Discriminator.default) val codecForFoo: Codec.AsObject[Foo] = deriveCodec(renaming.snakeCase, true, Discriminator.default) @@ -32,9 +32,9 @@ object NameTransformationSuiteCodecs extends Serializable { ) } -class NameTransformationSuite extends CirceSuite { - import NameTransformationExample._ - import NameTransformationSuiteCodecs._ +class TransformMemberNamesSuite extends CirceSuite { + import TransformMemberNamesExample._ + import TransformMemberNamesSuiteCodecs._ checkLaws("Codec[Foo]", CodecTests[Foo].codec) checkLaws("Codec[Foo] via Codec", CodecTests[Foo](codecForFoo, codecForFoo).codec)