Skip to content

Commit 006af57

Browse files
committed
Implement IgnoreTransientDefaultMarker
1 parent bed0a13 commit 006af57

File tree

4 files changed

+233
-6
lines changed

4 files changed

+233
-6
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.avsystem.commons
2+
package serialization
3+
4+
/**
5+
* Instructs [[GenCodec]] to <b>ignore</b> the [[transientDefault]] annotation when serializing a case class.
6+
* This ensures that even if a field's value is the same as its default, it will be <b>included</b> in the serialized
7+
* representation. Deserialization behavior remains <b>unchanged</b>. If a field is missing from the input, the default
8+
* value will be used as usual.
9+
*
10+
* This annotation can be helpful when using the same model class in multiple contexts with different serialization
11+
* formats that have conflicting requirements for handling default values.
12+
*
13+
* @see [[CustomMarkersOutputWrapper]]
14+
*/
15+
object IgnoreTransientDefaultMarker extends CustomEventMarker[Unit]
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package com.avsystem.commons
2+
package serialization
3+
4+
/**
5+
* [[Input]] implementation that adds additional markers [[CustomEventMarker]] to the provided [[Input]] instance
6+
*/
7+
final class CustomMarkersInputWrapper(
8+
override protected val wrapped: Input,
9+
markers: Set[CustomEventMarker[_]],
10+
) extends InputWrapper {
11+
12+
override def readList(): ListInput =
13+
new CustomMarkersInputWrapper.AdjustedListInput(super.readList(), markers)
14+
15+
override def readObject(): ObjectInput =
16+
new CustomMarkersInputWrapper.AdjustedObjectInput(super.readObject(), markers)
17+
18+
override def customEvent[T](marker: CustomEventMarker[T], value: T): Boolean =
19+
marker match {
20+
case marker if markers(marker) => true
21+
case _ => super.customEvent(marker, value)
22+
}
23+
}
24+
object CustomMarkersInputWrapper {
25+
def apply(input: Input, markers: CustomEventMarker[_]*): CustomMarkersInputWrapper =
26+
new CustomMarkersInputWrapper(input, markers.toSet)
27+
28+
private final class AdjustedListInput(
29+
override protected val wrapped: ListInput,
30+
markers: Set[CustomEventMarker[_]],
31+
) extends ListInputWrapper {
32+
override def nextElement(): Input = new CustomMarkersInputWrapper(super.nextElement(), markers)
33+
override def customEvent[T](marker: CustomEventMarker[T], value: T): Boolean =
34+
marker match {
35+
case marker if markers(marker) => true
36+
case _ => super.customEvent(marker, value)
37+
}
38+
}
39+
40+
private final class AdjustedFieldInput(
41+
override protected val wrapped: FieldInput,
42+
markers: Set[CustomEventMarker[_]],
43+
) extends FieldInputWrapper {
44+
45+
override def readList(): ListInput = new AdjustedListInput(super.readList(), markers)
46+
override def readObject(): ObjectInput = new AdjustedObjectInput(super.readObject(), markers)
47+
48+
override def customEvent[T](marker: CustomEventMarker[T], value: T): Boolean =
49+
marker match {
50+
case marker if markers(marker) => true
51+
case _ => super.customEvent(marker, value)
52+
}
53+
}
54+
55+
private final class AdjustedObjectInput(
56+
override protected val wrapped: ObjectInput,
57+
markers: Set[CustomEventMarker[_]],
58+
) extends ObjectInputWrapper {
59+
60+
override def nextField(): FieldInput = new AdjustedFieldInput(super.nextField(), markers)
61+
62+
override def peekField(name: String): Opt[FieldInput] =
63+
super.peekField(name).map(new AdjustedFieldInput(_, markers))
64+
65+
override def customEvent[T](marker: CustomEventMarker[T], value: T): Boolean =
66+
marker match {
67+
case marker if markers(marker) => true
68+
case _ => super.customEvent(marker, value)
69+
}
70+
}
71+
}
72+
73+
/**
74+
* [[Output]] implementation that adds additional markers [[CustomEventMarker]] to the provided [[Output]] instance
75+
*/
76+
final class CustomMarkersOutputWrapper(
77+
override protected val wrapped: Output,
78+
markers: Set[CustomEventMarker[_]],
79+
) extends OutputWrapper {
80+
81+
override def writeSimple(): SimpleOutput =
82+
new CustomMarkersOutputWrapper.AdjustedSimpleOutput(super.writeSimple(), markers)
83+
84+
override def writeList(): ListOutput =
85+
new CustomMarkersOutputWrapper.AdjustedListOutput(super.writeList(), markers)
86+
87+
override def writeObject(): ObjectOutput =
88+
new CustomMarkersOutputWrapper.AdjustedObjectOutput(super.writeObject(), markers)
89+
90+
override def customEvent[T](marker: CustomEventMarker[T], value: T): Boolean =
91+
marker match {
92+
case marker if markers(marker) => true
93+
case _ => super.customEvent(marker, value)
94+
}
95+
}
96+
97+
object CustomMarkersOutputWrapper {
98+
def apply(output: Output, markers: CustomEventMarker[_]*): CustomMarkersOutputWrapper =
99+
new CustomMarkersOutputWrapper(output, markers.toSet)
100+
101+
private final class AdjustedSimpleOutput(
102+
override protected val wrapped: SimpleOutput,
103+
markers: Set[CustomEventMarker[_]],
104+
) extends SimpleOutputWrapper {
105+
override def customEvent[T](marker: CustomEventMarker[T], value: T): Boolean =
106+
marker match {
107+
case marker if markers(marker) => true
108+
case _ => super.customEvent(marker, value)
109+
}
110+
}
111+
112+
private final class AdjustedListOutput(
113+
override protected val wrapped: ListOutput,
114+
markers: Set[CustomEventMarker[_]],
115+
) extends ListOutputWrapper {
116+
override def writeElement(): Output =
117+
new CustomMarkersOutputWrapper(super.writeElement(), markers)
118+
119+
override def customEvent[T](marker: CustomEventMarker[T], value: T): Boolean =
120+
marker match {
121+
case marker if markers(marker) => true
122+
case _ => super.customEvent(marker, value)
123+
}
124+
}
125+
126+
private final class AdjustedObjectOutput(
127+
override protected val wrapped: ObjectOutput,
128+
markers: Set[CustomEventMarker[_]],
129+
) extends ObjectOutputWrapper {
130+
override def writeField(key: String): Output =
131+
new CustomMarkersOutputWrapper(super.writeField(key), markers)
132+
133+
override def customEvent[T](marker: CustomEventMarker[T], value: T): Boolean =
134+
marker match {
135+
case marker if markers(marker) => true
136+
case _ => super.customEvent(marker, value)
137+
}
138+
}
139+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.avsystem.commons
2+
package serialization
3+
4+
import com.avsystem.commons.serialization.CodecTestData.HasDefaults
5+
6+
object IgnoreTransientDefaultMarkerTest {
7+
final case class NestedHasDefaults(
8+
@transientDefault flag: Boolean = false,
9+
obj: HasDefaults,
10+
list: Seq[HasDefaults],
11+
@transientDefault defaultObj: HasDefaults = HasDefaults(),
12+
)
13+
object NestedHasDefaults extends HasGenCodec[NestedHasDefaults]
14+
}
15+
16+
class IgnoreTransientDefaultMarkerTest extends AbstractCodecTest {
17+
import IgnoreTransientDefaultMarkerTest._
18+
19+
override type Raw = Any
20+
21+
def writeToOutput(write: Output => Unit): Any = {
22+
var result: Any = null
23+
write(CustomMarkersOutputWrapper(new SimpleValueOutput(result = _), IgnoreTransientDefaultMarker))
24+
result
25+
}
26+
27+
def createInput(raw: Any): Input = new SimpleValueInput(raw)
28+
29+
test("case class with default values") {
30+
testWrite(HasDefaults(str = "lol"), Map("str" -> "lol", "int" -> 42))
31+
testWrite(HasDefaults(43, "lol"), Map("int" -> 43, "str" -> "lol"))
32+
testWrite(HasDefaults(str = null), Map("str" -> null, "int" -> 42))
33+
testWrite(HasDefaults(str = "dafuq"), Map("str" -> "dafuq", "int" -> 42))
34+
}
35+
36+
test("nested case class with default values") {
37+
testWrite(
38+
value = NestedHasDefaults(
39+
flag = false,
40+
obj = HasDefaults(str = "lol"),
41+
list = Seq(HasDefaults(int = 43)),
42+
defaultObj = HasDefaults(),
43+
),
44+
expectedRepr = Map(
45+
"flag" -> false,
46+
"defaultObj" -> Map[String, Any]("str" -> "kek", "int" -> 42),
47+
"obj" -> Map[String, Any]("str" -> "lol", "int" -> 42),
48+
"list" -> List(Map[String, Any]("str" -> "kek", "int" -> 43)),
49+
),
50+
)
51+
}
52+
}

macros/src/main/scala/com/avsystem/commons/macros/serialization/GenCodecMacros.scala

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,20 @@ class GenCodecMacros(ctx: blackbox.Context) extends CodecMacroCommons(ctx) with
177177
if (isTransientDefault(p)) Some(p.defaultValue)
178178
else p.optionLike.map(ol => q"${ol.reference(Nil)}.none")
179179

180-
val writeArgs = q"output" :: q"${p.idx}" :: value :: transientValue.toList
180+
val writeArgsNoTransient = q"output" :: q"${p.idx}" :: List(value)
181+
val writeArgs = writeArgsNoTransient ::: transientValue.toList
181182
val writeTargs = if (isOptimizedPrimitive(p)) Nil else List(p.valueType)
182-
q"writeField[..$writeTargs](..$writeArgs)"
183+
q"""
184+
if (ignoreTransientDefault)
185+
writeField[..$writeTargs](..$writeArgsNoTransient)
186+
else
187+
writeField[..$writeTargs](..$writeArgs)
188+
"""
183189
}
184190

191+
def ignoreTransientDefaultCheck: Tree =
192+
q"val ignoreTransientDefault = output.customEvent($SerializationPkg.IgnoreTransientDefaultMarker, ())"
193+
185194
def writeFields: Tree = params match {
186195
case Nil =>
187196
if (canUseFields)
@@ -194,20 +203,32 @@ class GenCodecMacros(ctx: blackbox.Context) extends CodecMacroCommons(ctx) with
194203
"""
195204
case List(p: ApplyParam) =>
196205
if (canUseFields)
197-
writeField(p, q"value.${p.sym.name}")
206+
q"""
207+
$ignoreTransientDefaultCheck
208+
${writeField(p, q"value.${p.sym.name}")}
209+
"""
198210
else
199211
q"""
200212
val unapplyRes = $companion.$unapply[..${dtpe.typeArgs}](value)
201-
if(unapplyRes.isEmpty) unapplyFailed else ${writeField(p, q"unapplyRes.get")}
213+
if (unapplyRes.isEmpty) unapplyFailed
214+
else {
215+
$ignoreTransientDefaultCheck
216+
${writeField(p, q"unapplyRes.get")}
217+
}
202218
"""
203219
case _ =>
204220
if (canUseFields)
205-
q"..${params.map(p => writeField(p, q"value.${p.sym.name}"))}"
221+
q"""
222+
$ignoreTransientDefaultCheck
223+
..${params.map(p => writeField(p, q"value.${p.sym.name}"))}
224+
"""
206225
else
207226
q"""
208227
val unapplyRes = $companion.$unapply[..${dtpe.typeArgs}](value)
209-
if(unapplyRes.isEmpty) unapplyFailed else {
228+
if (unapplyRes.isEmpty) unapplyFailed
229+
else {
210230
val t = unapplyRes.get
231+
$ignoreTransientDefaultCheck
211232
..${params.map(p => writeField(p, q"t.${tupleGet(p.idx)}"))}
212233
}
213234
"""

0 commit comments

Comments
 (0)