Skip to content

Commit 1a7b545

Browse files
committed
Add @SnakeCaseJsonCodec alias
1 parent 4292155 commit 1a7b545

File tree

2 files changed

+48
-5
lines changed

2 files changed

+48
-5
lines changed

modules/annotations/shared/src/main/scala/io/circe/derivation/annotations/JsonCodec.scala

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ class JsonCodec(
1010
def macroTransform(annottees: Any*): Any = macro GenericJsonCodecMacros.jsonCodecAnnotationMacro
1111
}
1212

13+
class SnakeCaseJsonCodec extends scala.annotation.StaticAnnotation {
14+
def macroTransform(annottees: Any*): Any = macro GenericJsonCodecMacros.jsonCodecAnnotationMacro
15+
}
16+
17+
class KebabCaseJsonCodec extends scala.annotation.StaticAnnotation {
18+
def macroTransform(annottees: Any*): Any = macro GenericJsonCodecMacros.jsonCodecAnnotationMacro
19+
}
20+
1321
private[derivation] final class GenericJsonCodecMacros(val c: blackbox.Context) {
1422
import c.universe._
1523

@@ -55,12 +63,25 @@ private[derivation] final class GenericJsonCodecMacros(val c: blackbox.Context)
5563
private[this] val defaultCfg: Tree =
5664
q"_root_.io.circe.derivation.annotations.Configuration.default"
5765

66+
private[this] val snakeCaseMemberNamesCfg: Tree =
67+
q"_root_.io.circe.derivation.annotations.Configuration.default.withSnakeCaseMemberNames"
68+
69+
private[this] val kebabCaseMemberNamesCfg: Tree =
70+
q"_root_.io.circe.derivation.annotations.Configuration.default.withKebabCaseMemberNames"
71+
72+
private val snakeCaseAnnotationName = TypeName("SnakeCaseJsonCodec")
73+
5874
private[this] val (codecType: JsonCodecType, config: Tree) = {
59-
c.prefix.tree match {
60-
case q"new ${`macroName`}()" => (JsonCodecType.Both, defaultCfg)
61-
case q"new ${`macroName`}(config = $cfg)" => (codecFrom(c.typecheck(cfg)), cfg)
62-
case q"new ${`macroName`}($cfg)" => (codecFrom(c.typecheck(cfg)), cfg)
63-
case _ => c.abort(c.enclosingPosition, s"Unsupported arguments supplied to @$macroName")
75+
macroName match {
76+
case Ident(TypeName("SnakeCaseJsonCodec")) => (JsonCodecType.SnakeCaseJsonCodec, snakeCaseMemberNamesCfg)
77+
case Ident(TypeName("KebabCaseJsonCodec")) => (JsonCodecType.KebabCaseJsonCodec, kebabCaseMemberNamesCfg)
78+
case _ =>
79+
c.prefix.tree match {
80+
case q"new ${`macroName` }()" => (JsonCodecType.Both, defaultCfg)
81+
case q"new ${`macroName` }(config = $cfg)" => (codecFrom(c.typecheck(cfg)), cfg)
82+
case q"new ${`macroName` }($cfg)" => (codecFrom(c.typecheck(cfg)), cfg)
83+
case _ => c.abort(c.enclosingPosition, s"Unsupported arguments supplied to @$macroName")
84+
}
6485
}
6586
}
6687

@@ -117,6 +138,8 @@ private[derivation] final class GenericJsonCodecMacros(val c: blackbox.Context)
117138
}
118139
codecType match {
119140
case JsonCodecType.Both => List(decoder, encoder)
141+
case JsonCodecType.SnakeCaseJsonCodec => List(decoder, encoder)
142+
case JsonCodecType.KebabCaseJsonCodec => List(decoder, encoder)
120143
case JsonCodecType.DecodeOnly => List(decoder)
121144
case JsonCodecType.EncodeOnly => List(encoder)
122145
}
@@ -128,4 +151,6 @@ private object JsonCodecType {
128151
case object Both extends JsonCodecType
129152
case object DecodeOnly extends JsonCodecType
130153
case object EncodeOnly extends JsonCodecType
154+
case object SnakeCaseJsonCodec extends JsonCodecType
155+
case object KebabCaseJsonCodec extends JsonCodecType
131156
}

modules/annotations/shared/src/test/scala/io/circe/derivation/annotations/JsonCodecMacrosSuite.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,22 @@ class JsonCodecMacrosSuite extends CirceSuite {
199199
Encoder[CaseClassEncodeOnly]
200200
assertDoesNotCompile("Decoder[CaseClassEncodeOnly]")
201201
}
202+
203+
"@SnakeCaseJsonCodec" should "generate snake case JSON" in {
204+
@SnakeCaseJsonCodec case class CaseClass(fooSnake: String, barSnake: Int)
205+
206+
val generatedJson = Encoder[CaseClass].apply(CaseClass("foo", 1)).noSpaces
207+
val expectedJson = """{"foo_snake":"foo","bar_snake":1}"""
208+
209+
assertEq(expectedJson, generatedJson)
210+
}
211+
212+
"@KebabCaseJsonCodec" should "generate kebab case JSON" in {
213+
@KebabCaseJsonCodec case class CaseClass(fooKebab: String, barKebab: Int)
214+
215+
val generatedJson = Encoder[CaseClass].apply(CaseClass("foo", 1)).noSpaces
216+
val expectedJson = """{"foo-kebab":"foo","bar-kebab":1}"""
217+
218+
assertEq(expectedJson, generatedJson)
219+
}
202220
}

0 commit comments

Comments
 (0)