Skip to content

Commit decf7c2

Browse files
committed
Fix support of generic and named tuples for complex cases
1 parent fbaa32d commit decf7c2

File tree

3 files changed

+47
-24
lines changed

3 files changed

+47
-24
lines changed

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

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -778,18 +778,13 @@ object JsonCodecMaker {
778778
case _ => false
779779
}
780780

781-
def genericTupleTypeArgs(tpe: TypeRepr): List[TypeRepr] = tpe match {
782-
case AppliedType(_, List(typeArg, tail)) => typeArg.dealias :: genericTupleTypeArgs(tail)
783-
case _ => Nil
784-
}
785-
786781
def isNamedTuple(tpe: TypeRepr): Boolean = tpe match {
787782
case AppliedType(ntTpe, _) if ntTpe.dealias.typeSymbol.fullName == "scala.NamedTuple$.NamedTuple" => true
788783
case _ => false
789784
}
790785

791-
def namedGenericTupleNames(tpe: TypeRepr): List[String] = tpe match {
792-
case AppliedType(_, List(ConstantType(StringConstant(name)), tail)) => name :: namedGenericTupleNames(tail)
786+
def tupleTypeArgs(t: Type[?]): List[TypeRepr] = t match {
787+
case '[head *: tail] => TypeRepr.of[head] :: tupleTypeArgs(Type.of[tail])
793788
case _ => Nil
794789
}
795790

@@ -991,17 +986,21 @@ object JsonCodecMaker {
991986

992987
def getNamedTupleInfo(tpe: TypeRepr): NamedTupleInfo = namedTupleInfos.getOrElseUpdate(tpe, {
993988
tpe match {
994-
case AppliedType(_, List(nTpe @ AppliedType(_, nameConstants), tTpe)) =>
995-
val names =
996-
if (isGenericTuple(nTpe)) namedGenericTupleNames(nTpe)
997-
else nameConstants.collect { case ConstantType(StringConstant(x)) => x }
998-
val isGeneric = isGenericTuple(tTpe)
999-
val types =
1000-
if (isGeneric) genericTupleTypeArgs(tTpe)
1001-
else typeArgs(tTpe)
989+
case AppliedType(_, List(nTpe, tTpe)) =>
990+
// Borrowed from an amazing work of Aleksander Rainko: https://github.com/arainko/ducktape/blob/8d779f0303c23fd45815d3574467ffc321a8db2b/ducktape/src/main/scala/io/github/arainko/ducktape/internal/Structure.scala#L188-L199
991+
val names = tupleTypeArgs(nTpe.asType).map { case ConstantType(StringConstant(n)) => n }
992+
val typeArgs = tupleTypeArgs(tTpe.asType)
993+
val size = typeArgs.size
994+
val tupleTpe =
995+
if (size <= 22) defn.TupleClass(size).typeRef.appliedTo(typeArgs)
996+
else typeArgs.foldRight(TypeRepr.of[EmptyTuple]) {
997+
val tupleCons = TypeRepr.of[*:]
998+
(curr, acc) => tupleCons.appliedTo(curr :: acc :: Nil)
999+
}
1000+
val isGeneric = isGenericTuple(tupleTpe)
10021001
val noSymbol = Symbol.noSymbol
10031002
var i = - 1
1004-
NamedTupleInfo(tpe, tTpe.asType, isGeneric, List(names.zip(types).map { case (name, fTpe) =>
1003+
NamedTupleInfo(tpe, tupleTpe.asType, isGeneric, List(names.zip(typeArgs).map { case (name, fTpe) =>
10051004
i += 1
10061005
val mappedName = cfg.fieldNameMapper(name).getOrElse(name)
10071006
FieldInfo(noSymbol, mappedName, noSymbol, None, fTpe, false, false, i)
@@ -2677,7 +2676,7 @@ object JsonCodecMaker {
26772676
} else if (isTuple(tpe)) withDecoderFor(methodKey, default, in) { (in, default) =>
26782677
val isGeneric = isGenericTuple(tpe)
26792678
val indexedTypes =
2680-
if (isGeneric) genericTupleTypeArgs(tpe)
2679+
if (isGeneric) tupleTypeArgs(tpe.asType)
26812680
else typeArgs(tpe)
26822681
val valDefs = indexedTypes.map {
26832682
var i = 0
@@ -3161,7 +3160,7 @@ object JsonCodecMaker {
31613160
} else if (isTuple(tpe)) withEncoderFor(methodKey, m, out) { (out, x) =>
31623161
val isGeneric = isGenericTuple(tpe)
31633162
val indexedTypes =
3164-
if (isGeneric) genericTupleTypeArgs(tpe)
3163+
if (isGeneric) tupleTypeArgs(tpe.asType)
31653164
else typeArgs(tpe)
31663165
val writeFields = indexedTypes.map {
31673166
var i = 0

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,13 @@ class JsonCodecMakerNewTypeSpec extends VerifyingSpec {
6363
"""[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,"24"]""")
6464
}
6565
"serialize and deserialize Scala 3 generic tuples" in {
66-
verifySerDeser(make[(Byte *: Short *: Int *: Long *: EmptyTuple)],
67-
(1: Byte) *: (2: Short) *: 3 *: 4L *: EmptyTuple,
68-
"""[1,2,3,4]""")
66+
verifySerDeser(make[Byte *: Short *: Int *: Long *: EmptyTuple],
67+
(1: Byte) *: (2: Short) *: 3 *: 4L *: EmptyTuple, """[1,2,3,4]""")
68+
verifySerDeser(make[Byte *: Short *: Tuple2[Int, Long]],
69+
(1: Byte) *: (2: Short) *: (3, 4L), """[1,2,3,4]""")
70+
verifySerDeser(make[Tuple.Concat[(Byte, Short), (Int, Long)]], (1: Byte, 2: Short, 3, 4L), """[1,2,3,4]""")
71+
verifySerDeser(make[Tuple.Drop[(Long, Int, String), 1]], (1, "VVV"), """[1,"VVV"]""")
72+
verifySerDeser(make[Tuple.Take[(Int, String, Long), 2]], (1, "VVV"), """[1,"VVV"]""")
6973
}
7074
"serialize and deserialize Scala 3 immutable array" in {
7175
val json = """{"aa":[[1,2],[3,4]],"a":[1,2,3,4]}"""

jsoniter-scala-next-tests/shared/src/test/scala-3/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMakerNamedTupleSpec.scala

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,34 @@ class JsonCodecMakerNamedTupleSpec extends VerifyingSpec {
77
"JsonCodecMaker.make generate codecs which" should {
88
"serialize and deserialize Scala 3 named tuples" in {
99
verifySerDeser(make[(i: Int, s: String)], (i = 1, s = "VVV"), """{"i":1,"s":"VVV"}""")
10+
verifySerDeser(make[NamedTuple.Reverse[(i: Int, s: String)]], (s = "VVV", i = 1), """{"s":"VVV","i":1}""")
11+
verifySerDeser(make[NamedTuple.Concat[(i: Int), (s: String)]], (i = 1, s = "VVV"), """{"i":1,"s":"VVV"}""")
12+
verifySerDeser(make[NamedTuple.Tail[(l: Long, i: Int, s: String)]], (i = 1, s = "VVV"), """{"i":1,"s":"VVV"}""")
13+
verifySerDeser(make[NamedTuple.Init[(i: Int, s: String, l: Long)]], (i = 1, s = "VVV"), """{"i":1,"s":"VVV"}""")
14+
verifySerDeser(make[NamedTuple.Drop[(l: Long, i: Int, s: String), 1]], (i = 1, s = "VVV"), """{"i":1,"s":"VVV"}""")
15+
verifySerDeser(make[NamedTuple.Take[(i: Int, s: String, l: Long), 2]], (i = 1, s = "VVV"), """{"i":1,"s":"VVV"}""")
16+
verifySerDeser(make[NamedTuple.Split[(i: Int, s: String, l: Long), 2]], ((i = 1, s = "VVV"), (l = 2L)),
17+
"""[{"i":1,"s":"VVV"},{"l":2}]""")
18+
verifySerDeser(make[NamedTuple.Zip[(i: Int, s: String), (i: Long, s: String)]], (i = (1, 2L), s = ("VVV", "WWW")),
19+
"""{"i":[1,2],"s":["VVV","WWW"]}""")
20+
}
21+
"serialize and deserialize Scala 3 tuples derived from named tuples" in {
22+
verifySerDeser(make[NamedTuple.DropNames[(i: Int, s: String)]], (1, "VVV"), """[1,"VVV"]""")
23+
}
24+
"serialize and deserialize Scala 3 tuples derived from tuples" in {
25+
verifySerDeser(make[Tuple.Reverse[(Long, Int, Short, Byte)]], (1: Byte, 2: Short, 3, 4L), """[1,2,3,4]""")
1026
}
1127
"serialize and deserialize Scala 3 named tuples with generic tuple for names" in {
1228
verifySerDeser(make[NamedTuple.NamedTuple["i" *: "s" *: EmptyTuple, (Int, String)]],
13-
(i = 1, s = "VVV"), """{"i":1,"s":"VVV"}""")
29+
(i = 1, s = "VVV"), """{"i":1,"s":"VVV"}""")
30+
verifySerDeser(make[NamedTuple.NamedTuple["i" *: ("s", "l"), (Int, String, Long)]],
31+
(i = 1, s = "VVV", l = 2L), """{"i":1,"s":"VVV","l":2}""")
1432
}
1533
"serialize and deserialize Scala 3 named tuples with generic tuple for value types" in {
16-
verifySerDeser(make[NamedTuple.NamedTuple[("i", "s"), Int *: String *: EmptyTuple]],
17-
(i = 1, s = "VVV"), """{"i":1,"s":"VVV"}""")
34+
verifySerDeser(make[NamedTuple.NamedTuple[("i", "s"), Int *: String *: EmptyTuple]],
35+
(i = 1, s = "VVV"), """{"i":1,"s":"VVV"}""")
36+
verifySerDeser(make[NamedTuple.NamedTuple[("i", "s", "l"), Int *: Tuple2[String, Long]]],
37+
(i = 1, s = "VVV", l = 2L), """{"i":1,"s":"VVV","l":2}""")
1838
}
1939
"serialize and deserialize generic Scala 3 named tuples" in {
2040
type GenericNamedTuple[A, B] = (a: A, b: B)

0 commit comments

Comments
 (0)