Skip to content

Commit 04b6d21

Browse files
authored
Make tagName configurable on the upickle pickler basis (#603)
This functionaly was removed in #579 when we made it configurable via `@key`, but it's easy enough to support both config mechanisms with `@key` taking precedence but `def tagName` being used as a fallback
1 parent 3e1d457 commit 04b6d21

File tree

7 files changed

+62
-22
lines changed

7 files changed

+62
-22
lines changed

upickle/core/src/upickle/core/Config.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ package upickle.core
22

33
// Common things for derivation
44
trait Config {
5+
/**
6+
* Specifies the name of the field used to distinguish different `case class`es under
7+
* a `sealed trait`. Defaults to `$type`, but can be configured globally by overriding
8+
* [[tagName]], or on a per-`sealed trait` basis via the `@key` annotation
9+
*/
510
def tagName = Annotator.defaultTagKey
11+
612
/**
713
* Whether to use the fully-qualified name of `case class`es and `case object`s which
814
* are part of `sealed trait` hierarchies when serializing them and writing their `$type`

upickle/implicits/src-2/upickle/implicits/internal/Macros.scala

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ object Macros {
104104

105105
}
106106

107-
def mergeTrait(tagKey: String, subtrees: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree
107+
def mergeTrait(tagKey: Option[String], subtrees: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree
108108

109109
def derive(tpe: c.Type) = {
110110
if (tpe.typeSymbol.asClass.isTrait || (tpe.typeSymbol.asClass.isAbstractClass && !tpe.typeSymbol.isJava)) {
@@ -127,7 +127,7 @@ object Macros {
127127
"https://com-lihaoyi.github.io/upickle/#ManualSealedTraitPicklers"
128128
fail(tpe, msg)
129129
}else{
130-
val tagKey = customKey(clsSymbol).getOrElse(Annotator.defaultTagKey)
130+
val tagKey = customKey(clsSymbol)
131131
val subTypes = fleshedOutSubtypes(tpe).toSeq.sortBy(_.typeSymbol.fullName)
132132
// println("deriveTrait")
133133
val subDerives = subTypes.map(subCls => q"implicitly[${typeclassFor(subCls)}]")
@@ -241,7 +241,11 @@ object Macros {
241241
).decodedName.toString
242242
)
243243

244-
q"${c.prefix}.annotate($derived, $tagKey, $tagValue, $shortTagValue)"
244+
val tagKeyExpr = tagKey match {
245+
case Some(v) => q"$v"
246+
case None => q"${c.prefix}.tagName"
247+
}
248+
q"${c.prefix}.annotate($derived, $tagKeyExpr, $tagValue, $shortTagValue)"
245249
}
246250
}
247251

@@ -363,8 +367,12 @@ object Macros {
363367
"""
364368
}
365369

366-
override def mergeTrait(tagKey: String, subtrees: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree = {
367-
q"${c.prefix}.Reader.merge[$targetType]($tagKey, ..$subtrees)"
370+
override def mergeTrait(tagKey: Option[String], subtrees: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree = {
371+
val tagKeyExpr = tagKey match {
372+
case Some(v) => q"$v"
373+
case None => q"${c.prefix}.tagName"
374+
}
375+
q"${c.prefix}.Reader.merge[$targetType]($tagKeyExpr, ..$subtrees)"
368376
}
369377
}
370378

@@ -436,7 +444,7 @@ object Macros {
436444
"""
437445
}
438446

439-
override def mergeTrait(tagKey: String, subtree: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree = {
447+
override def mergeTrait(tagKey: Option[String], subtree: Seq[Tree], subtypes: Seq[Type], targetType: c.Type): Tree = {
440448
q"${c.prefix}.Writer.merge[$targetType](..$subtree)"
441449
}
442450
}

upickle/implicits/src-3/upickle/implicits/Readers.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ trait ReadersVersionSpecific
7878
inline if macros.isSingleton[T] then
7979
annotate[T](
8080
SingletonReader[T](macros.getSingleton[T]),
81-
macros.tagKey[T],
81+
macros.tagKey[T](outerThis),
8282
macros.tagName[T],
8383
macros.shortTagName[T]
8484
)
8585
else if macros.isMemberOfSealedHierarchy[T] then
8686
annotate[T](
8787
reader,
88-
macros.tagKey[T],
88+
macros.tagKey[T](outerThis),
8989
macros.tagName[T],
9090
macros.shortTagName[T],
9191
)
@@ -97,7 +97,7 @@ trait ReadersVersionSpecific
9797
.toList
9898
.asInstanceOf[List[Reader[_ <: T]]]
9999

100-
Reader.merge[T](macros.tagKey[T], readers: _*)
100+
Reader.merge[T](macros.tagKey[T](outerThis), readers: _*)
101101
}
102102

103103
inline def macroRAll[T](using m: Mirror.Of[T]): Reader[T] = inline m match {
@@ -109,7 +109,7 @@ trait ReadersVersionSpecific
109109
inline given superTypeReader[T: Mirror.ProductOf, V >: T : Reader : Mirror.SumOf]
110110
(using NotGiven[CurrentlyDeriving[V]]): Reader[T] = {
111111
val actual = implicitly[Reader[V]].asInstanceOf[TaggedReader[T]]
112-
val tagKey = macros.tagKey[T]
112+
val tagKey = macros.tagKey[T](outerThis)
113113
val tagName = macros.tagName[T]
114114
val shortTagName = macros.shortTagName[T]
115115
new TaggedReader.Leaf(tagKey, tagName, shortTagName, actual.findReader(tagName))

upickle/implicits/src-3/upickle/implicits/Writers.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ trait WritersVersionSpecific
1313
with Annotator
1414
with CaseClassReadWriters:
1515

16-
val outerThis = this
1716
inline def macroW[T: ClassTag](using m: Mirror.Of[T]): Writer[T] = inline m match {
1817
case m: Mirror.ProductOf[T] =>
1918

@@ -46,15 +45,15 @@ trait WritersVersionSpecific
4645
inline if macros.isSingleton[T] then
4746
annotate[T](
4847
SingletonWriter[T](null.asInstanceOf[T]),
49-
macros.tagKey[T],
48+
macros.tagKey[T](outerThis),
5049
macros.tagName[T],
5150
macros.shortTagName[T],
5251
Annotator.Checker.Val(macros.getSingleton[T]),
5352
)
5453
else if macros.isMemberOfSealedHierarchy[T] then
5554
annotate[T](
5655
writer,
57-
macros.tagKey[T],
56+
macros.tagKey[T](outerThis),
5857
macros.tagName[T],
5958
macros.shortTagName[T],
6059
Annotator.Checker.Cls(implicitly[ClassTag[T]].runtimeClass),

upickle/implicits/src-3/upickle/implicits/macros.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,21 +181,23 @@ inline def isMemberOfSealedHierarchy[T]: Boolean = ${ isMemberOfSealedHierarchyI
181181
def isMemberOfSealedHierarchyImpl[T](using Quotes, Type[T]): Expr[Boolean] =
182182
Expr(sealedHierarchyParents[T].nonEmpty)
183183

184-
inline def tagKey[T]: String = ${ tagKeyImpl[T] }
185-
def tagKeyImpl[T](using Quotes, Type[T]): Expr[String] =
184+
inline def tagKey[T](inline thisOuter: upickle.core.Types with upickle.implicits.MacrosCommon): String = ${ tagKeyImpl[T]('thisOuter) }
185+
def tagKeyImpl[T](using Quotes, Type[T])(thisOuter: Expr[upickle.core.Types with upickle.implicits.MacrosCommon]): Expr[String] =
186186
import quotes.reflect._
187187

188188
// `case object`s extend from `Mirror`, which is `sealed` and will never have a `@key` annotation
189189
// so we need to filter it out to ensure it doesn't trigger an error in `tagKeyFromParents`
190190
val mirrorType = Symbol.requiredClass("scala.deriving.Mirror")
191-
192-
Expr(MacrosCommon.tagKeyFromParents(
191+
MacrosCommon.tagKeyFromParents(
193192
Type.show[T],
194193
sealedHierarchyParents[T].filterNot(_ == mirrorType),
195194
extractKey,
196195
(_: Symbol).name,
197196
report.errorAndAbort,
198-
))
197+
) match{
198+
case Some(v) => Expr(v)
199+
case None => '{${thisOuter}.tagName}
200+
}
199201

200202
inline def tagName[T]: String = ${ tagNameImpl[T] }
201203
def tagNameImpl[T](using Quotes, Type[T]): Expr[String] =

upickle/implicits/src/upickle/implicits/MacrosCommon.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package upickle.implicits
22

3-
trait MacrosCommon extends upickle.core.Config
3+
trait MacrosCommon extends upickle.core.Config with upickle.core.Types{
4+
val outerThis = this
5+
}
46

57
object MacrosCommon {
68
def tagKeyFromParents[P](
@@ -9,16 +11,16 @@ object MacrosCommon {
911
getKey: P => Option[String],
1012
getName: P => String,
1113
fail: String => Nothing,
12-
): String =
14+
): Option[String] =
1315
/**
1416
* Valid cases are:
1517
*
1618
* 1. None of the parents have a `@key` annotation
1719
* 2. All of the parents have the same `@key` annotation
1820
*/
1921
sealedParents.flatMap(getKey(_)) match {
20-
case Nil => upickle.core.Annotator.defaultTagKey
21-
case keys @ (key :: _) if keys.length == sealedParents.length && keys.distinct.length == 1 => key
22+
case Nil => None
23+
case keys @ (key :: _) if keys.length == sealedParents.length && keys.distinct.length == 1 => Some(key)
2224
case keys =>
2325
fail(
2426
s"Type $typeName inherits from multiple parent types with different discriminator keys:\n\n" +

upickle/test/src/upickle/MacroTests.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,20 @@ object UnknownKeys{
125125
override def allowUnknownKeys = false
126126
}
127127
}
128+
129+
object TagName{
130+
object TagNamePickler extends upickle.AttributeTagged {
131+
override def tagName = "_tag"
132+
}
133+
134+
sealed trait Foo
135+
case class Bar(x: Int) extends Foo
136+
case class Qux(s: String) extends Foo
137+
138+
implicit val barRw: TagNamePickler.ReadWriter[Bar] = TagNamePickler.macroRW
139+
implicit val quxRw: TagNamePickler.ReadWriter[Qux] = TagNamePickler.macroRW
140+
implicit val fooRw: TagNamePickler.ReadWriter[Foo] = TagNamePickler.macroRW
141+
}
128142
object MacroTests extends TestSuite {
129143

130144
// Doesn't work :(
@@ -849,5 +863,14 @@ object MacroTests extends TestSuite {
849863
)
850864

851865
}
866+
867+
test("tagName"){
868+
val customPicklerTest = new TestUtil(TagName.TagNamePickler)
869+
customPicklerTest.rw(
870+
TagName.Bar(123),
871+
"""{"_tag": "Bar", "x": 123}"""
872+
)
873+
874+
}
852875
}
853876
}

0 commit comments

Comments
 (0)