-
Notifications
You must be signed in to change notification settings - Fork 23
Allow Output to ignore @transientDefault #633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
006af57
3982060
4276c36
37c2a77
b19248f
53780f9
e614e2d
c1307e7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -312,11 +312,13 @@ object GenCodec extends RecursiveAutoCodecs with TupleGenCodecs { | |
} | ||
|
||
trait SizedCodec[T] extends GenCodec[T] { | ||
def size(value: T): Int | ||
def size(value: T): Int = size(value, Opt.Empty) | ||
|
||
def size(value: T, output: Opt[SequentialOutput]): Int | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe we'd like to have |
||
|
||
protected final def declareSizeFor(output: SequentialOutput, value: T): Unit = | ||
if (output.sizePolicy != SizePolicy.Ignored) { | ||
output.declareSize(size(value)) | ||
output.declareSize(size(value, output.opt)) | ||
} | ||
} | ||
|
||
|
@@ -336,8 +338,8 @@ object GenCodec extends RecursiveAutoCodecs with TupleGenCodecs { | |
object OOOFieldsObjectCodec { | ||
// this was introduced so that transparent wrapper cases are possible in flat sealed hierarchies | ||
final class Transformed[A, B](val wrapped: OOOFieldsObjectCodec[B], onWrite: A => B, onRead: B => A) extends OOOFieldsObjectCodec[A] { | ||
def size(value: A): Int = | ||
wrapped.size(onWrite(value)) | ||
def size(value: A, output: Opt[SequentialOutput]): Int = | ||
wrapped.size(onWrite(value), output) | ||
|
||
def readObject(input: ObjectInput, outOfOrderFields: FieldValues): A = | ||
onRead(wrapped.readObject(input, outOfOrderFields)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.avsystem.commons | ||
package serialization | ||
|
||
/** | ||
* Instructs [[GenCodec]] to <b>ignore</b> the [[transientDefault]] annotation when serializing a case class. | ||
* This ensures that even if a field's value is the same as its default, it will be <b>included</b> in the serialized | ||
* representation. Deserialization behavior remains <b>unchanged</b>. If a field is missing from the input, the default | ||
* value will be used as usual. | ||
* | ||
* This marker can be helpful when using the same model class in multiple contexts with different serialization | ||
* formats that have conflicting requirements for handling default values. | ||
* | ||
* @see [[CustomMarkersOutputWrapper]] for an easy way to add markers to existing [[Output]] implementations | ||
*/ | ||
object IgnoreTransientDefaultMarker extends CustomEventMarker[Unit] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package com.avsystem.commons | ||
sebaciv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
package serialization | ||
|
||
trait AcceptsAdditionalCustomMarkers extends AcceptsCustomEvents { | ||
|
||
protected def markers: Set[CustomEventMarker[?]] | ||
|
||
override def customEvent[T](marker: CustomEventMarker[T], event: T): Boolean = | ||
markers(marker) || super.customEvent(marker, event) | ||
} | ||
|
||
/** | ||
* [[Input]] implementation that adds additional markers [[CustomEventMarker]] to the provided [[Input]] instance | ||
*/ | ||
final class CustomMarkersInputWrapper private( | ||
override protected val wrapped: Input, | ||
override protected val markers: Set[CustomEventMarker[?]], | ||
) extends InputWrapper with AcceptsAdditionalCustomMarkers { | ||
|
||
override def readList(): ListInput = | ||
new CustomMarkersInputWrapper.AdjustedListInput(super.readList(), markers) | ||
|
||
override def readObject(): ObjectInput = | ||
new CustomMarkersInputWrapper.AdjustedObjectInput(super.readObject(), markers) | ||
} | ||
object CustomMarkersInputWrapper { | ||
halotukozak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def apply(input: Input, markers: CustomEventMarker[?]*): CustomMarkersInputWrapper = | ||
CustomMarkersInputWrapper(input, markers.toSet) | ||
|
||
def apply(input: Input, markers: Set[CustomEventMarker[?]]): CustomMarkersInputWrapper = | ||
new CustomMarkersInputWrapper(input, markers) | ||
|
||
private final class AdjustedListInput( | ||
override protected val wrapped: ListInput, | ||
override protected val markers: Set[CustomEventMarker[?]], | ||
) extends ListInputWrapper with AcceptsAdditionalCustomMarkers { | ||
override def nextElement(): Input = new CustomMarkersInputWrapper(super.nextElement(), markers) | ||
} | ||
|
||
private final class AdjustedFieldInput( | ||
override protected val wrapped: FieldInput, | ||
override protected val markers: Set[CustomEventMarker[?]], | ||
) extends FieldInputWrapper with AcceptsAdditionalCustomMarkers { | ||
|
||
override def readList(): ListInput = new AdjustedListInput(super.readList(), markers) | ||
override def readObject(): ObjectInput = new AdjustedObjectInput(super.readObject(), markers) | ||
} | ||
|
||
private final class AdjustedObjectInput( | ||
override protected val wrapped: ObjectInput, | ||
override protected val markers: Set[CustomEventMarker[?]], | ||
) extends ObjectInputWrapper with AcceptsAdditionalCustomMarkers { | ||
|
||
override def nextField(): FieldInput = new AdjustedFieldInput(super.nextField(), markers) | ||
override def peekField(name: String): Opt[FieldInput] = | ||
super.peekField(name).map(new AdjustedFieldInput(_, markers)) | ||
} | ||
} | ||
|
||
/** | ||
* [[Output]] implementation that adds additional markers [[CustomEventMarker]] to the provided [[Output]] instance | ||
*/ | ||
final class CustomMarkersOutputWrapper private( | ||
override protected val wrapped: Output, | ||
override protected val markers: Set[CustomEventMarker[?]], | ||
) extends OutputWrapper with AcceptsAdditionalCustomMarkers { | ||
|
||
override def writeSimple(): SimpleOutput = | ||
new CustomMarkersOutputWrapper.AdjustedSimpleOutput(super.writeSimple(), markers) | ||
|
||
override def writeList(): ListOutput = | ||
new CustomMarkersOutputWrapper.AdjustedListOutput(super.writeList(), markers) | ||
|
||
override def writeObject(): ObjectOutput = | ||
new CustomMarkersOutputWrapper.AdjustedObjectOutput(super.writeObject(), markers) | ||
} | ||
|
||
object CustomMarkersOutputWrapper { | ||
halotukozak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def apply(output: Output, markers: CustomEventMarker[?]*): CustomMarkersOutputWrapper = | ||
CustomMarkersOutputWrapper(output, markers.toSet) | ||
|
||
def apply(output: Output, markers: Set[CustomEventMarker[?]]): CustomMarkersOutputWrapper = | ||
new CustomMarkersOutputWrapper(output, markers) | ||
|
||
private final class AdjustedSimpleOutput( | ||
override protected val wrapped: SimpleOutput, | ||
override protected val markers: Set[CustomEventMarker[?]], | ||
) extends SimpleOutputWrapper with AcceptsAdditionalCustomMarkers | ||
|
||
private final class AdjustedListOutput( | ||
override protected val wrapped: ListOutput, | ||
override protected val markers: Set[CustomEventMarker[?]], | ||
) extends ListOutputWrapper with AcceptsAdditionalCustomMarkers { | ||
|
||
override def writeElement(): Output = | ||
new CustomMarkersOutputWrapper(super.writeElement(), markers) | ||
} | ||
|
||
private final class AdjustedObjectOutput( | ||
override protected val wrapped: ObjectOutput, | ||
override protected val markers: Set[CustomEventMarker[?]], | ||
) extends ObjectOutputWrapper with AcceptsAdditionalCustomMarkers { | ||
|
||
override def writeField(key: String): Output = | ||
new CustomMarkersOutputWrapper(super.writeField(key), markers) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package com.avsystem.commons | ||
package serialization | ||
|
||
import com.avsystem.commons.serialization.CodecTestData.HasDefaults | ||
|
||
object IgnoreTransientDefaultMarkerTest { | ||
final case class NestedHasDefaults( | ||
@transientDefault flag: Boolean = false, | ||
obj: HasDefaults, | ||
list: Seq[HasDefaults], | ||
@transientDefault defaultObj: HasDefaults = HasDefaults(), | ||
) | ||
object NestedHasDefaults extends HasGenCodec[NestedHasDefaults] | ||
|
||
final case class HasOptParam( | ||
@transientDefault flag: Boolean = false, | ||
@optionalParam str: Opt[String] = Opt.Empty, | ||
) | ||
object HasOptParam extends HasGenCodec[HasOptParam] | ||
} | ||
|
||
class IgnoreTransientDefaultMarkerTest extends AbstractCodecTest { | ||
sebaciv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import IgnoreTransientDefaultMarkerTest.* | ||
|
||
override type Raw = Any | ||
|
||
def writeToOutput(write: Output => Unit): Any = { | ||
var result: Any = null | ||
write(CustomMarkersOutputWrapper(new SimpleValueOutput(v => result = v), IgnoreTransientDefaultMarker)) | ||
result | ||
} | ||
|
||
def createInput(raw: Any): Input = | ||
CustomMarkersInputWrapper(new SimpleValueInput(raw), IgnoreTransientDefaultMarker) | ||
|
||
test("write case class with default values") { | ||
testWrite(HasDefaults(str = "lol"), Map("str" -> "lol", "int" -> 42)) | ||
testWrite(HasDefaults(43, "lol"), Map("int" -> 43, "str" -> "lol")) | ||
testWrite(HasDefaults(str = null), Map("str" -> null, "int" -> 42)) | ||
testWrite(HasDefaults(str = "dafuq"), Map("str" -> "dafuq", "int" -> 42)) | ||
} | ||
|
||
//noinspection RedundantDefaultArgument | ||
test("read case class with default values") { | ||
halotukozak marked this conversation as resolved.
Show resolved
Hide resolved
|
||
testRead(Map("str" -> "lol", "int" -> 42), HasDefaults(str = "lol", int = 42)) | ||
testRead(Map("str" -> "lol"), HasDefaults(str = "lol", int = 42)) | ||
testRead(Map("int" -> 43, "str" -> "lol"), HasDefaults(int = 43, str = "lol")) | ||
testRead(Map("str" -> null, "int" -> 42), HasDefaults(str = null, int = 42)) | ||
testRead(Map("str" -> null), HasDefaults(str = null, int = 42)) | ||
testRead(Map(), HasDefaults(str = "dafuq", int = 42)) | ||
} | ||
|
||
test("write case class with opt values") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assumed all option-like types are treated the same by com.avsystem.commons.serialization.optionalParam, so there should be no need to write test for every type. |
||
testWrite(HasOptParam(str = "lol".opt), Map("flag" -> false, "str" -> "lol")) | ||
testWrite(HasOptParam(), Map("flag" -> false)) | ||
} | ||
|
||
//noinspection RedundantDefaultArgument | ||
test("write nested case class with default values") { | ||
testWrite( | ||
value = NestedHasDefaults( | ||
flag = false, | ||
obj = HasDefaults(str = "lol"), | ||
list = Seq(HasDefaults(int = 43)), | ||
defaultObj = HasDefaults(), | ||
), | ||
expectedRepr = Map( | ||
"flag" -> false, | ||
"defaultObj" -> Map[String, Any]("str" -> "kek", "int" -> 42), | ||
"obj" -> Map[String, Any]("str" -> "lol", "int" -> 42), | ||
"list" -> List(Map[String, Any]("str" -> "kek", "int" -> 43)), | ||
), | ||
) | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.