Skip to content

Commit 46b3bff

Browse files
committed
Fix #1274 by allowing usage of regular and nested options + faster derivation of codecs for product types
1 parent 416f2e0 commit 46b3bff

File tree

3 files changed

+34
-30
lines changed

3 files changed

+34
-30
lines changed

jsoniter-scala-macros/shared/src/main/scala-2/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMaker.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,10 +1557,9 @@ object JsonCodecMaker {
15571557
def genReadVal(types: List[Type], default: Tree, isStringified: Boolean, discriminator: Tree): Tree = {
15581558
val tpe = types.head
15591559
val implValueCodec = findImplicitValueCodec(types)
1560-
val methodKey = MethodKey(tpe, isStringified && (isCollection(tpe) || isOption(tpe, types.tail)), discriminator)
1561-
val decodeMethodName = decodeMethodNames.get(methodKey)
1560+
lazy val methodKey = MethodKey(tpe, isStringified && (isCollection(tpe) || isOption(tpe, types.tail)), discriminator)
1561+
lazy val decodeMethodName = decodeMethodNames.get(methodKey)
15621562
if (implValueCodec.nonEmpty) q"$implValueCodec.decodeValue(in, $default)"
1563-
else if (decodeMethodName.isDefined) q"${decodeMethodName.get}(in, $default)"
15641563
else if (tpe =:= typeOf[String]) q"in.readString($default)"
15651564
else if (tpe =:= definitions.BooleanTpe || tpe =:= typeOf[java.lang.Boolean]) {
15661565
if (isStringified) q"in.readStringAsBoolean()"
@@ -1622,7 +1621,8 @@ object JsonCodecMaker {
16221621
in.rollbackToken()
16231622
new _root_.scala.Some(${genReadVal(tpe1 :: types, genNullValue(tpe1 :: types), isStringified, EmptyTree)})
16241623
}"""
1625-
} else if (tpe <:< typeOf[Array[_]] || isImmutableArraySeq(tpe) ||
1624+
} else if (decodeMethodName.isDefined) q"${decodeMethodName.get}(in, $default)"
1625+
else if (tpe <:< typeOf[Array[_]] || isImmutableArraySeq(tpe) ||
16261626
isMutableArraySeq(tpe)) withDecoderFor(methodKey, default) {
16271627
val tpe1 = typeArg1(tpe)
16281628
val growArray =
@@ -2050,10 +2050,9 @@ object JsonCodecMaker {
20502050
def genWriteVal(m: Tree, types: List[Type], isStringified: Boolean, discriminator: Tree): Tree = {
20512051
val tpe = types.head
20522052
val implValueCodec = findImplicitValueCodec(types)
2053-
val methodKey = MethodKey(tpe, isStringified && (isCollection(tpe) || isOption(tpe, types.tail)), discriminator)
2054-
val encodeMethodName = encodeMethodNames.get(methodKey)
2053+
lazy val methodKey = MethodKey(tpe, isStringified && (isCollection(tpe) || isOption(tpe, types.tail)), discriminator)
2054+
lazy val encodeMethodName = encodeMethodNames.get(methodKey)
20552055
if (implValueCodec.nonEmpty) q"$implValueCodec.encodeValue($m, out)"
2056-
else if (encodeMethodName.isDefined) q"${encodeMethodName.get}($m, out)"
20572056
else if (tpe =:= typeOf[String]) q"out.writeVal($m)"
20582057
else if (tpe =:= definitions.BooleanTpe || tpe =:= typeOf[java.lang.Boolean] || tpe =:= definitions.ByteTpe ||
20592058
tpe =:= typeOf[java.lang.Byte] || tpe =:= definitions.ShortTpe || tpe =:= typeOf[java.lang.Short] ||
@@ -2074,7 +2073,8 @@ object JsonCodecMaker {
20742073
} else if (isOption(tpe, types.tail)) {
20752074
q"""if ($m ne _root_.scala.None) ${genWriteVal(q"$m.get", typeArg1(tpe) :: types, isStringified, EmptyTree)}
20762075
else out.writeNull()"""
2077-
} else if (tpe <:< typeOf[Array[_]] || isImmutableArraySeq(tpe) ||
2076+
} else if (encodeMethodName.isDefined) q"${encodeMethodName.get}($m, out)"
2077+
else if (tpe <:< typeOf[Array[_]] || isImmutableArraySeq(tpe) ||
20782078
isMutableArraySeq(tpe)) withEncoderFor(methodKey, m) {
20792079
val tpe1 = typeArg1(tpe)
20802080
if (isImmutableArraySeq(tpe)) {

jsoniter-scala-macros/shared/src/main/scala-3/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMaker.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,10 +2084,9 @@ object JsonCodecMaker {
20842084
useDiscriminator: Boolean, in: Expr[JsonReader])(using Quotes): Expr[T] =
20852085
val tpe = types.head
20862086
val implCodec = findImplicitValueCodec(types)
2087-
val methodKey = DecoderMethodKey(tpe, isStringified && (isCollection(tpe) || isOption(tpe, types.tail)), useDiscriminator)
2088-
val decodeMethodSym = decodeMethodSyms.get(methodKey)
2087+
lazy val methodKey = DecoderMethodKey(tpe, isStringified && (isCollection(tpe) || isOption(tpe, types.tail)), useDiscriminator)
2088+
lazy val decodeMethodSym = decodeMethodSyms.get(methodKey)
20892089
if (implCodec.nonEmpty) '{ ${implCodec.get.asExprOf[JsonValueCodec[T]]}.decodeValue($in, $default) }
2090-
else if (decodeMethodSym.isDefined) Apply(Ref(decodeMethodSym.get), List(in.asTerm, default.asTerm)).asExprOf[T]
20912090
else if (tpe =:= TypeRepr.of[String]) '{ $in.readString(${default.asExprOf[String]}) }.asExprOf[T]
20922091
else if (tpe =:= TypeRepr.of[Boolean]) {
20932092
if (isStringified) '{ $in.readStringAsBoolean() }.asExprOf[T]
@@ -2185,7 +2184,8 @@ object JsonCodecMaker {
21852184
new Some($readVal1)
21862185
}
21872186
}.asExprOf[T]
2188-
} else if (tpe <:< TypeRepr.of[Array[_]] || tpe <:< TypeRepr.of[immutable.ArraySeq[_]] ||
2187+
} else if (decodeMethodSym.isDefined) Apply(Ref(decodeMethodSym.get), List(in.asTerm, default.asTerm)).asExprOf[T]
2188+
else if (tpe <:< TypeRepr.of[Array[_]] || tpe <:< TypeRepr.of[immutable.ArraySeq[_]] ||
21892189
tpe.typeSymbol.fullName == "scala.IArray$package$.IArray" ||
21902190
tpe <:< TypeRepr.of[mutable.ArraySeq[_]]) withDecoderFor(methodKey, default, in) { (in, default) =>
21912191
val tpe1 = typeArg1(tpe)
@@ -2738,11 +2738,10 @@ object JsonCodecMaker {
27382738
out: Expr[JsonWriter])(using Quotes): Expr[Unit] =
27392739
val tpe = types.head
27402740
val implCodec = findImplicitValueCodec(types)
2741-
val methodKey = EncoderMethodKey(tpe, isStringified && (isCollection(tpe) || isOption(tpe, types.tail)),
2741+
lazy val methodKey = EncoderMethodKey(tpe, isStringified && (isCollection(tpe) || isOption(tpe, types.tail)),
27422742
optWriteDiscriminator.map(x => (x.fieldName, x.fieldValue)))
2743-
val encodeMethodSym = encodeMethodSyms.get(methodKey)
2743+
lazy val encodeMethodSym = encodeMethodSyms.get(methodKey)
27442744
if (implCodec.nonEmpty) '{ ${implCodec.get.asExprOf[JsonValueCodec[T]]}.encodeValue($m, $out) }
2745-
else if (encodeMethodSym.isDefined) Apply(Ref(encodeMethodSym.get), List(m.asTerm, out.asTerm)).asExprOf[Unit]
27462745
else if (tpe =:= TypeRepr.of[String]) '{ $out.writeVal(${m.asExprOf[String]}) }
27472746
else if (tpe =:= TypeRepr.of[Boolean]) {
27482747
if (isStringified) '{ $out.writeValAsString(${m.asExprOf[Boolean]}) }
@@ -2823,7 +2822,8 @@ object JsonCodecMaker {
28232822
if ($x ne None) ${genWriteVal('{ $x.get }, tpe1 :: types, isStringified, None, out)}
28242823
else $out.writeNull()
28252824
}
2826-
} else if (tpe <:< TypeRepr.of[Array[_]] || tpe <:< TypeRepr.of[immutable.ArraySeq[_]] ||
2825+
} else if (encodeMethodSym.isDefined) Apply(Ref(encodeMethodSym.get), List(m.asTerm, out.asTerm)).asExprOf[Unit]
2826+
else if (tpe <:< TypeRepr.of[Array[_]] || tpe <:< TypeRepr.of[immutable.ArraySeq[_]] ||
28272827
tpe.typeSymbol.fullName == "scala.IArray$package$.IArray" ||
28282828
tpe <:< TypeRepr.of[mutable.ArraySeq[_]]) withEncoderFor(methodKey, m, out) { (out, x) =>
28292829
val tpe1 = typeArg1(tpe)

jsoniter-scala-macros/shared/src/test/scala/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMakerSpec.scala

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,33 +1173,37 @@ class JsonCodecMakerSpec extends VerifyingSpec {
11731173
"""{"param1":"A","param2":"B","payload":{"x":[-1.0,1,4.0E20],"y":{"xx":true,"yy":false,"zz":null},"z":"Z"},"param3":"C"}""")
11741174
}
11751175
"serialize and deserialize nested options without loss of information" in {
1176-
case class NestedOptions(x: Option[Option[Option[String]]] = _root_.scala.None)
1176+
case class NestedOptions(y: Option[String], x: Option[Option[Option[String]]] = _root_.scala.None)
11771177

11781178
val codecOfNestedOptions = make[NestedOptions]
1179-
verifySerDeser(codecOfNestedOptions, NestedOptions(_root_.scala.None), """{}""")
1180-
verifyDeser(codecOfNestedOptions, NestedOptions(_root_.scala.None), """{"x":null}""")
1181-
verifySerDeser(codecOfNestedOptions, NestedOptions(_root_.scala.Some(_root_.scala.Some(_root_.scala.Some("VVV")))),
1179+
verifyDeser(codecOfNestedOptions,
1180+
NestedOptions(_root_.scala.None, _root_.scala.None), """{"y":null,"x":null}""")
1181+
verifySerDeser(codecOfNestedOptions,
1182+
NestedOptions(_root_.scala.Some("VVV"), _root_.scala.None), """{"y":"VVV"}""")
1183+
verifySerDeser(codecOfNestedOptions,
1184+
NestedOptions(_root_.scala.None, _root_.scala.Some(_root_.scala.Some(_root_.scala.Some("VVV")))),
11821185
"""{"x":{"type":"Some","value":{"type":"Some","value":"VVV"}}}""")
1183-
verifySerDeser(codecOfNestedOptions, NestedOptions(_root_.scala.Some(_root_.scala.None)),
1184-
"""{"x":{"type":"None"}}""")
1185-
verifySerDeser(codecOfNestedOptions, NestedOptions(_root_.scala.Some(_root_.scala.Some(_root_.scala.None))),
1186+
verifySerDeser(codecOfNestedOptions,
1187+
NestedOptions(_root_.scala.None, _root_.scala.Some(_root_.scala.None)), """{"x":{"type":"None"}}""")
1188+
verifySerDeser(codecOfNestedOptions,
1189+
NestedOptions(_root_.scala.None, _root_.scala.Some(_root_.scala.Some(_root_.scala.None))),
11861190
"""{"x":{"type":"Some","value":{"type":"None"}}}""")
11871191
}
11881192
"serialize and deserialize Option[Option[_]] to distinguish `null` field values and missing fields" in {
1189-
case class Model(field1: Option[Option[String]], field2: Option[Option[String]] = _root_.scala.None)
1193+
case class Model(field1: Option[String], field2: Option[Option[String]] = _root_.scala.None)
11901194

11911195
verifySerDeser(make[List[Model]](CodecMakerConfig.withSkipNestedOptionValues(true)),
11921196
List(
1193-
Model(_root_.scala.Some(_root_.scala.Some("VVV")), _root_.scala.Some(_root_.scala.Some("WWW"))),
1197+
Model(_root_.scala.Some("VVV"), _root_.scala.Some(_root_.scala.Some("WWW"))),
11941198
Model(_root_.scala.None),
1195-
Model(_root_.scala.Some(_root_.scala.None), _root_.scala.Some(_root_.scala.None))),
1196-
"""[{"field1":"VVV","field2":"WWW"},{},{"field1":null,"field2":null}]""")
1199+
Model(_root_.scala.None, _root_.scala.Some(_root_.scala.None))),
1200+
"""[{"field1":"VVV","field2":"WWW"},{},{"field2":null}]""")
11971201
verifySerDeser(makeWithSkipNestedOptionValues[List[Model]],
11981202
List(
1199-
Model(_root_.scala.Some(_root_.scala.Some("VVV")), _root_.scala.Some(_root_.scala.Some("WWW"))),
1203+
Model(_root_.scala.Some("VVV"), _root_.scala.Some(_root_.scala.Some("WWW"))),
12001204
Model(_root_.scala.None),
1201-
Model(_root_.scala.Some(_root_.scala.None), _root_.scala.Some(_root_.scala.None))),
1202-
"""[{"field1":"VVV","field2":"WWW"},{},{"field1":null,"field2":null}]""")
1205+
Model(_root_.scala.None, _root_.scala.Some(_root_.scala.None))),
1206+
"""[{"field1":"VVV","field2":"WWW"},{},{"field2":null}]""")
12031207
}
12041208
"serialize and deserialize Nullable[_] to distinguish `null` field values and missing fields using a custom value codec" in {
12051209
sealed trait Nullable[+A]

0 commit comments

Comments
 (0)