-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extension of JsonCodec to have parity features with generic.extra #91
Changes from all commits
1f9f3c1
600f84a
b7e2fb8
af80a4e
a117913
40f365c
c660501
9f37826
4d55ecc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,5 @@ target/ | |
.project | ||
.classpath | ||
tmp/ | ||
.bloop | ||
.metals |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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] => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, I hadn't noticed these changes. @aparo, is there a reason to change these? I fixed this a few weeks ago because I was getting inaccurate warnings in some contexts. |
||
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( | ||
|
@@ -101,8 +101,15 @@ 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" | ||
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]($cfgTransformMemberNames, $cfgUseDefaults, $cfgDiscriminator)""", | ||
q"""implicit val $encoderName: $AsObjectEncoderClass[$Type] = | ||
_root_.io.circe.derivation.deriveEncoder[$Type]($cfgNameTransformation)""", | ||
_root_.io.circe.derivation.deriveEncoder[$Type]($cfgTransformMemberNames, $cfgDiscriminator)""", | ||
q"""implicit val $codecName: $AsObjectCodecClass[$Type] = | ||
_root_.io.circe.derivation.deriveCodec[$Type]($cfgNameTransformation)""" | ||
_root_.io.circe.derivation.deriveCodec[$Type]($cfgTransformMemberNames, $cfgUseDefaults, $cfgDiscriminator)""" | ||
) | ||
} else { | ||
val tparamNames = tparams.map(_.name) | ||
|
@@ -134,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]($cfgTransformMemberNames, $cfgUseDefaults, $cfgDiscriminator)""", | ||
q"""implicit def $encoderName[..$tparams](implicit ..$encodeParams): $AsObjectEncoderClass[$Type] = | ||
_root_.io.circe.derivation.deriveEncoder[$Type]($cfgNameTransformation)""", | ||
_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)""" | ||
_root_.io.circe.derivation.deriveCodec[$Type]($cfgTransformMemberNames, $cfgUseDefaults, $cfgDiscriminator)""" | ||
) | ||
} | ||
codecType match { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}}}""" | ||
) | ||
|
||
} | ||
|
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we support ADTs we should probably have
transformConstructorNames
as well, but I can add that in a follow-up.