Skip to content

Commit 839ad8d

Browse files
authored
Merge pull request tethys-json#303 from dos65/default_fields_fix
fix: support default values for case class fields
2 parents efadf64 + 946f7e1 commit 839ad8d

File tree

7 files changed

+83
-24
lines changed

7 files changed

+83
-24
lines changed

modules/macro-derivation/src/main/scala-2/tethys/derivation/impl/CaseClassUtils.scala

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ trait CaseClassUtils extends LoggingUtils {
1010
import c.universe._
1111

1212
case class CaseClassDefinition(tpe: Type, fields: List[CaseClassField])
13-
case class CaseClassField(name: String, tpe: Type)
13+
case class CaseClassField(name: String, tpe: Type, defaultValue: Option[Tree])
1414

1515
def caseClassDefinition[A: WeakTypeTag]: CaseClassDefinition = caseClassDefinition(weakTypeOf[A])
1616

1717
def caseClassDefinition(tpe: Type): CaseClassDefinition = {
1818
val ctor = getConstructor(tpe)
1919
CaseClassDefinition(
2020
tpe = tpe,
21-
fields = ctor.paramLists.head.map(constructorParameterToCaseClassField(tpe))
21+
fields = ctor.paramLists.head.zipWithIndex.map{ case (sym, idx) => constructorParameterToCaseClassField(tpe)(idx, sym) }
2222
)
2323
}
2424

@@ -39,13 +39,21 @@ trait CaseClassUtils extends LoggingUtils {
3939
}
4040
}
4141

42-
private def constructorParameterToCaseClassField(tpe: Type)(param: Symbol): CaseClassField = {
42+
private def constructorParameterToCaseClassField(tpe: Type)(idx: Int, param: Symbol): CaseClassField = {
4343
val possibleRealType = tpe.decls.collectFirst {
4444
case s if s.name == param.name => s.typeSignatureIn(tpe).finalResultType
4545
}
46+
4647
CaseClassField(
4748
name = param.name.decodedName.toString,
48-
tpe = possibleRealType.getOrElse(param.typeSignatureIn(tpe))
49+
tpe = possibleRealType.getOrElse(param.typeSignatureIn(tpe)),
50+
defaultValue =
51+
if (param.asTerm.isParamWithDefault) {
52+
val methodName = TermName(s"apply$$default$$${idx + 1}")
53+
val select = q"${tpe.companion.typeSymbol.asClass.module}.$methodName"
54+
Some(select)
55+
} else
56+
None
4957
)
5058
}
5159
}

modules/macro-derivation/src/main/scala-2/tethys/derivation/impl/derivation/ReaderDerivation.scala

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ trait ReaderDerivation
4141
tpe: Type,
4242
jsonName: String,
4343
value: TermName,
44-
isInitialized: TermName) extends ReaderField
44+
isInitialized: TermName,
45+
defaultValue: Option[Tree]) extends ReaderField
4546

4647
private case class ExtractedField(name: String,
4748
tpe: Type,
@@ -80,7 +81,8 @@ trait ReaderDerivation
8081
tpe = field.tpe,
8182
jsonName = field.name,
8283
value = TermName(c.freshName(field.name + "Value")),
83-
isInitialized = TermName(c.freshName(field.name + "Init"))
84+
isInitialized = TermName(c.freshName(field.name + "Init")),
85+
defaultValue = field.defaultValue
8486
)
8587
})
8688

@@ -275,17 +277,20 @@ trait ReaderDerivation
275277
}
276278

277279
private def allocateVariables(readerFields: List[ReaderField], typeDefaultValues: List[(Type, TermName)]): List[Tree] = {
278-
val possibleValues: List[(TermName, Type)] = readerFields.flatMap {
280+
val possibleValues: List[(TermName, Type, Option[Tree])] = readerFields.flatMap {
279281
case f: SimpleField =>
280-
List(f.value -> f.tpe)
282+
List((f.value, f.tpe, f.defaultValue))
281283
case f: ExtractedField =>
282-
(f.value, f.tpe) :: f.args.map(arg => arg.value -> arg.field.tpe)
284+
(f.value, f.tpe, None) :: f.args.map(arg => (arg.value, arg.field.tpe, None))
283285
case f: FromExtractedReader =>
284-
(f.value, f.tpe) :: f.args.map(arg => arg.value -> arg.field.tpe)
286+
((f.value, f.tpe, None)) :: f.args.map(arg => (arg.value, arg.field.tpe, None))
285287
}
286288

287289
val (_, values) = possibleValues.foldLeft(List[TermName](), List[Tree]()) {
288-
case ((allocated, trees), (value, tpe)) if !allocated.contains(value) =>
290+
case ((allocated, trees), (value, tpe, Some(defaultTree))) =>
291+
val tree = q"var $value: $tpe = $defaultTree"
292+
(value :: allocated, tree :: trees)
293+
case ((allocated, trees), (value, tpe, defaultTreeOpt)) if !allocated.contains(value) =>
289294
val tree = q"var $value: $tpe = ${typeDefaultValues.find(_._1 =:= tpe).get._2}"
290295
(value :: allocated, tree :: trees)
291296

@@ -295,14 +300,14 @@ trait ReaderDerivation
295300
val inits = readerFields
296301
.flatMap {
297302
case f: SimpleField =>
298-
List(f.isInitialized)
303+
List((f.isInitialized, f.defaultValue.isDefined))
299304
case f: ExtractedField =>
300-
f.isInitialized :: f.args.map(_.isInitialized)
305+
(f.isInitialized, false) :: f.args.map(a => (a.isInitialized, false))
301306
case f: FromExtractedReader =>
302-
f.isInitialized :: f.args.map(_.isInitialized)
307+
(f.isInitialized, false) :: f.args.map(a => (a.isInitialized, false))
303308
}
304309
.distinct
305-
.map(term => q"var $term: Boolean = false")
310+
.map{ case (term, initialized) => q"var $term: Boolean = $initialized"}
306311

307312
val tempIterators = readerFields.collect {
308313
case f: FromExtractedReader =>

modules/macro-derivation/src/main/scala-3/tethys/derivation/impl/derivation/ReaderDerivation.scala

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ trait ReaderDerivation extends ReaderBuilderCommons {
147147
}
148148
it.nextToken()
149149

150+
$defaultValuesExpr.foreach { case (name, tpeName, defaultValue) =>
151+
readFields.getOrElseUpdate(name, MutableMap(tpeName -> defaultValue))
152+
}
153+
150154
$possiblyNotInitializedExpr.foreach { case (name, tpeName, defaultValue) =>
151155
readFields.getOrElseUpdate(name, MutableMap(tpeName -> defaultValue))
152156
}
@@ -232,9 +236,6 @@ trait ReaderDerivation extends ReaderBuilderCommons {
232236
Expr.block(res, '{ () })
233237
}
234238

235-
$defaultValuesExpr.foreach { case (name, defaultValue) =>
236-
resultFields.getOrElseUpdate(name, defaultValue)
237-
}
238239

239240
val notReadAfterExtractingFields: Set[String] =
240241
Set.from(${ Varargs(classFields.map(field => Expr(field.name))) }) -- resultFields.keySet
@@ -377,10 +378,10 @@ trait ReaderDerivation extends ReaderBuilderCommons {
377378
(readersExpr, fieldsWithoutReadersExpr)
378379
}
379380

380-
private def allocateDefaultValuesFromDefinition[T: Type]: Expr[Map[String, Any]] = {
381+
private def allocateDefaultValuesFromDefinition[T: Type]: Expr[List[(String, String, Any)]] = {
381382
val tpe = TypeRepr.of[T]
382383

383-
val res = tpe.typeSymbol.caseFields.flatMap {
384+
val res = tpe.typeSymbol.caseFields.collect {
384385
case sym if sym.flags.is(Flags.HasDefault) =>
385386
val comp = sym.owner.companionClass
386387
val mod = Ref(sym.owner.companionModule)
@@ -397,11 +398,14 @@ trait ReaderDerivation extends ReaderBuilderCommons {
397398
)
398399

399400
val defaultValueTerm = mod.select(defaultValueMethodSym)
400-
Some(Expr.ofTuple(Expr(sym.name) -> defaultValueTerm.asExprOf[Any]))
401-
case _ => None
401+
val appliedTypes = if tpe.typeArgs.nonEmpty then defaultValueTerm.appliedToTypes(tpe.typeArgs) else defaultValueTerm
402+
Expr.ofTuple(
403+
Expr(sym.name),
404+
Expr(tpe.memberType(sym).getDealiasFullName),
405+
appliedTypes.asExprOf[Any]
406+
)
402407
}
403-
404-
'{ Map(${ Varargs(res) }: _*) }
408+
Expr.ofList(res)
405409
}
406410

407411
private def allocateTypeReadersInfos(readerFields: List[ReaderField]): List[(TypeRepr, Term)] = {

modules/macro-derivation/src/test/scala-2.13+/tethys/derivation/SemiautoReaderDerivationTest.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,4 +323,23 @@ class SemiautoReaderDerivationTest extends AnyFlatSpec with Matchers {
323323
))
324324
} should have message "Illegal json at '[ROOT]': unexpected field 'not_id_param', expected one of 'some_param', 'id_param', 'simple'"
325325
}
326+
327+
it should "derive reader for class with default params" in {
328+
implicit val reader: JsonReader[DefaultField[Int]] = jsonReader[DefaultField[Int]]
329+
330+
read[DefaultField[Int]](obj(
331+
"value" -> 1,
332+
"default" -> false
333+
)) shouldBe DefaultField[Int](
334+
value = 1,
335+
default = false
336+
)
337+
338+
read[DefaultField[Int]](obj(
339+
"value" -> 1,
340+
)) shouldBe DefaultField[Int](
341+
value = 1,
342+
default = true
343+
)
344+
}
326345
}

modules/macro-derivation/src/test/scala-2.13+/tethys/derivation/package.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ package object derivation {
2424
case class SeqMaster4(a: Seq[Int])
2525

2626
case class CamelCaseNames(someParam: Int, IDParam: Int, simple: Int)
27+
28+
case class DefaultField[T](value: T, default: Boolean = true)
2729
}

modules/macro-derivation/src/test/scala-3/tethys/derivation/SemiautoReaderDerivationTest.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,4 +352,23 @@ class SemiautoReaderDerivationTest extends AnyFlatSpec with Matchers {
352352
token(ParametrizedEnum.TWO.toString)
353353
) shouldBe ParametrizedEnum.TWO
354354
}
355+
356+
it should "derive reader for class with default params" in {
357+
implicit val reader: JsonReader[DefaultField[Int]] = jsonReader[DefaultField[Int]]
358+
359+
read[DefaultField[Int]](obj(
360+
"value" -> 1,
361+
"default" -> false
362+
)) shouldBe DefaultField[Int](
363+
value = 1,
364+
default = false
365+
)
366+
367+
read[DefaultField[Int]](obj(
368+
"value" -> 1
369+
)) shouldBe DefaultField[Int](
370+
value = 1,
371+
default = true
372+
)
373+
}
355374
}

modules/macro-derivation/src/test/scala-3/tethys/derivation/package.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ package object derivation {
3333
case ONE extends ParametrizedEnum(1)
3434
case TWO extends ParametrizedEnum(2)
3535
}
36+
37+
case class DefaultField[T](value: T, default: Boolean = true)
3638
}

0 commit comments

Comments
 (0)