Skip to content

Commit a7b8c30

Browse files
committed
Fix #82 by adding support of Scala operators in the field names of case classes
1 parent c786c47 commit a7b8c30

File tree

2 files changed

+24
-20
lines changed

2 files changed

+24
-20
lines changed

macros/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMaker.scala

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.github.plokhotnyuk.jsoniter_scala.macros
33
import java.lang.Character._
44
import java.time._
55

6-
import com.github.plokhotnyuk.jsoniter_scala.core.{JsonValueCodec, JsonReader, JsonWriter}
6+
import com.github.plokhotnyuk.jsoniter_scala.core.{JsonReader, JsonValueCodec, JsonWriter}
77

88
import scala.annotation.StaticAnnotation
99
import scala.annotation.meta.field
@@ -296,7 +296,7 @@ object JsonCodecMaker {
296296
def getFieldAnnotations(tpe: Type): Map[String, FieldAnnotations] = tpe.members.collect {
297297
case m: TermSymbol if m.annotations.exists(a => a.tree.tpe =:= c.weakTypeOf[named]
298298
|| a.tree.tpe =:= c.weakTypeOf[transient] || a.tree.tpe =:= c.weakTypeOf[stringified]) =>
299-
val fieldName = m.name.toString.trim // FIXME: Why is there a space at the end of field name?!
299+
val fieldName = m.name.decodedName.toString.trim // FIXME: Why is there a space at the end of field name?!
300300
val named = m.annotations.filter(_.tree.tpe =:= c.weakTypeOf[named])
301301
if (named.size > 1) fail(s"Duplicated '${typeOf[named]}' defined for '$fieldName' of '$tpe'.")
302302
val trans = m.annotations.filter(_.tree.tpe =:= c.weakTypeOf[transient])
@@ -328,12 +328,12 @@ object JsonCodecMaker {
328328
def getDefaults(tpe: Type): Map[String, Tree] = {
329329
val module = getModule(tpe)
330330
getParams(tpe).zipWithIndex.collect { case (p, i) if p.isParamWithDefault =>
331-
(p.name.toString, q"$module.${TermName("$lessinit$greater$default$" + (i + 1))}")
331+
(p.name.decodedName.toString, q"$module.${TermName("$lessinit$greater$default$" + (i + 1))}")
332332
}(breakOut)
333333
}
334334

335335
def getMembers(annotations: Map[String, FieldAnnotations], tpe: c.universe.Type): Seq[MethodSymbol] = {
336-
def nonTransient(m: MethodSymbol): Boolean = annotations.get(m.name.toString).fold(true)(!_.transient)
336+
def nonTransient(m: MethodSymbol): Boolean = annotations.get(m.name.decodedName.toString).fold(true)(!_.transient)
337337

338338
tpe.members.collect {
339339
case m: MethodSymbol if m.isCaseAccessor && nonTransient(m) => m
@@ -355,11 +355,8 @@ object JsonCodecMaker {
355355
def getStringified(annotations: Map[String, FieldAnnotations], name: String): Boolean =
356356
annotations.get(name).fold(false)(_.stringified)
357357

358-
def fixMinuses(name: String): String =
359-
if (name.indexOf("$minus") == -1) name else name.replace("$minus", "-")
360-
361358
def getMappedName(annotations: Map[String, FieldAnnotations], defaultName: String): String =
362-
annotations.get(defaultName).fold(codecConfig.fieldNameMapper(fixMinuses(defaultName)))(_.name)
359+
annotations.get(defaultName).fold(codecConfig.fieldNameMapper(defaultName))(_.name)
363360

364361
def getCollisions(names: Traversable[String]): Traversable[String] =
365362
names.groupBy(identity).collect { case (x, xs) if xs.size > 1 => x }
@@ -614,7 +611,7 @@ object JsonCodecMaker {
614611
} else if (tpe.typeSymbol.asClass.isCaseClass) withDecoderFor(methodKey, default) {
615612
val annotations = getFieldAnnotations(tpe)
616613

617-
def name(m: MethodSymbol): String = getMappedName(annotations, m.name.toString)
614+
def name(m: MethodSymbol): String = getMappedName(annotations, m.name.decodedName.toString)
618615

619616
def hashCode(m: MethodSymbol): Int = {
620617
val cs = name(m).toCharArray
@@ -626,7 +623,7 @@ object JsonCodecMaker {
626623
(if (discriminator.isEmpty) Seq.empty else Seq(codecConfig.discriminatorFieldName)) ++ members.map(name))
627624
val params = getParams(tpe)
628625
val required = params.collect {
629-
case p if !p.isParamWithDefault && !isContainer(p.typeSignature) => p.name.toString
626+
case p if !p.isParamWithDefault && !isContainer(p.typeSignature) => p.name.decodedName.toString
630627
}
631628
val reqVarNum = required.size
632629
val lastReqVarIndex = reqVarNum >> 5
@@ -652,14 +649,15 @@ object JsonCodecMaker {
652649
val defaults = getDefaults(tpe)
653650
val readVars = members.map { m =>
654651
val tpe = methodType(m)
655-
q"var ${TermName(s"_${m.name}")}: $tpe = ${defaults.getOrElse(m.name.toString, nullValue(tpe))}"
652+
q"var ${TermName(s"_${m.name}")}: $tpe = ${defaults.getOrElse(m.name.decodedName.toString, nullValue(tpe))}"
656653
}
657654
val readFields = groupByOrdered(members)(hashCode).map { case (hashCode, ms) =>
658655
val checkNameAndReadValue = ms.foldRight(unexpectedFieldHandler) { case (m, acc) =>
656+
val decodedName = m.name.decodedName.toString
659657
val varName = TermName(s"_${m.name}")
660-
val isStringified = getStringified(annotations, m.name.toString)
658+
val isStringified = getStringified(annotations, decodedName)
661659
val readValue = q"$varName = ${genReadVal(methodType(m), q"$varName", isStringified)}"
662-
val resetReqFieldFlag = bitmasks.getOrElse(m.name.toString, EmptyTree)
660+
val resetReqFieldFlag = bitmasks.getOrElse(decodedName, EmptyTree)
663661
q"""if (in.isCharBufEqualsTo(l, ${name(m)})) {
664662
..$readValue
665663
..$resetReqFieldFlag
@@ -788,9 +786,10 @@ object JsonCodecMaker {
788786
val defaults = getDefaults(tpe)
789787
val writeFields = members.map { m =>
790788
val tpe = methodType(m)
791-
val name = getMappedName(annotations, m.name.toString)
792-
val isStringified = getStringified(annotations, m.name.toString)
793-
defaults.get(m.name.toString) match {
789+
val decodedName = m.name.decodedName.toString
790+
val mappedName = getMappedName(annotations, decodedName)
791+
val isStringified = getStringified(annotations, decodedName)
792+
defaults.get(decodedName) match {
794793
case Some(d) =>
795794
if (isContainer(tpe)) {
796795
val nonEmptyAndDefaultMatchingCheck =
@@ -805,13 +804,13 @@ object JsonCodecMaker {
805804
else genWriteVal(q"v", tpe, isStringified)
806805
q"""val v = x.$m
807806
if ($nonEmptyAndDefaultMatchingCheck) {
808-
..${genWriteConstantKey(name)}
807+
..${genWriteConstantKey(mappedName)}
809808
..$writeVal
810809
}"""
811810
} else {
812811
q"""val v = x.$m
813812
if (v != $d) {
814-
..${genWriteConstantKey(name)}
813+
..${genWriteConstantKey(mappedName)}
815814
..${genWriteVal(q"v", tpe, isStringified)}
816815
}"""
817816
}
@@ -823,11 +822,11 @@ object JsonCodecMaker {
823822
else genWriteVal(q"v", tpe, isStringified)
824823
q"""val v = x.$m
825824
if ($nonEmptyCheck) {
826-
..${genWriteConstantKey(name)}
825+
..${genWriteConstantKey(mappedName)}
827826
..$writeVal
828827
}"""
829828
} else {
830-
q"""..${genWriteConstantKey(name)}
829+
q"""..${genWriteConstantKey(mappedName)}
831830
..${genWriteVal(q"x.$m", tpe, isStringified)}"""
832831
}
833832
}

macros/src/test/scala/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMakerSpec.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ class JsonCodecMakerSpec extends WordSpec with Matchers {
113113

114114
val codecOfUTF8KeysAndValues: JsonValueCodec[UTF8KeysAndValues] = make[UTF8KeysAndValues](CodecMakerConfig())
115115

116+
case class Operators(`=<>!#%^&|*/\\~+-:$`: Int)
117+
116118
//FIXME: case classes with field annotation(s) should be defined in source file before `make` call for them
117119
case class NameOverridden(@named("new_" + "name") oldName: String)
118120

@@ -804,6 +806,9 @@ class JsonCodecMakerSpec extends WordSpec with Matchers {
804806
"{\"\\u10d2\\u10d0\\u10e1\\u10d0\\u10e6\\u10d4\\u10d1\\u10d8\":\"\\u10d5\\u10d5\\u10d5\\b\\f\\n\\r\\t/\"}".getBytes(UTF_8),
805807
WriterConfig(escapeUnicode = true))
806808
}
809+
"serialize and deserialize case classes with Scala operators in field names" in {
810+
verifySerDeser(make[Operators](CodecMakerConfig()), Operators(7), """{"=<>!#%^&|*/\\~+-:$":7}""".getBytes(UTF_8))
811+
}
807812
"deserialize but don't serialize default values of case classes that defined for fields" in {
808813
verifySer(codecOfDefaults, defaults, "{}".getBytes)
809814
verifySer(codecOfDefaults, defaults.copy(oc = None, l = Nil), """{}""".getBytes)

0 commit comments

Comments
 (0)