Zyle Moore
11/19/2024, 3:32 AMephemient
11/19/2024, 5:54 AMdecodeSequentially() = true
only works with decodeCollectionSize() != -1
ephemient
11/19/2024, 5:55 AMephemient
11/19/2024, 5:56 AMZyle Moore
11/19/2024, 7:21 AMZyle Moore
11/19/2024, 7:56 AMDaniel Pitts
11/19/2024, 3:29 PMBen Woodworth
11/19/2024, 7:16 PMannotations
, have a @SerialInfo
-marked BathesdaNullTerminatedString
annotation, and maybe a SerialDescriptor.isBathesdaNullTerminatedString
extension property that the encoder/decoder use. (And definitely pick better names for these. I haven't played much of Elder Scrolls 😅)Zyle Moore
11/19/2024, 9:56 PMZyle Moore
11/23/2024, 2:01 AM@Target
annotation. The documentation mentions it's recommended, but is it actually required?Ben Woodworth
11/23/2024, 2:09 AMBen Woodworth
11/23/2024, 2:12 AMZyle Moore
11/23/2024, 3:58 AMencodeElement
, would I need to implement CompositeEncoder
instead of AbstractEncoder()
, because the abstract one has a final
implementation of encodeStringElement
? Otherwise, which component has the context to be able to encode an @NullTerminated val someString: String
?Ben Woodworth
11/23/2024, 4:17 AMZyle Moore
11/23/2024, 4:26 AMZyle Moore
11/23/2024, 5:26 AMZyle Moore
11/23/2024, 5:36 AMencodeSerializableValue
. Two encoded ints would have a length of 8, so it would need to encode [8, [4 bytes], [4 bytes]]
. My current implementation is to serialize this struct independently of the "main" Encoding. Some structs have a maximum size, but I need to encode the actual size, before the actual data. The second Encoder would receive repeat(2) { encodeInt }
, the initial Encoder would receive repeat(9) { encodeByte }
Are there common pitfalls to doing this this way? Mostly in the realm of newing up serializers with separate Encoders mid-encoding. Or is there a more conventional way of prepending the expected payload size?Ben Woodworth
11/23/2024, 5:26 PMephemient
11/24/2024, 2:47 AMephemient
11/24/2024, 2:51 AMZyle Moore
11/29/2024, 10:20 PMserializersModule
to determine which serializers to use
• Format creates (En|De)coder
instance, which uses a (Read|Writ)er
wrapping/returning string/byte array
• Encoder uses format's serializersModule
• Encoder primarily uses SerialDescriptor
and SerialInfo
annotations for decision making (like the one mentioned earlier in the thread)
• Encoder overrides primitive and serializable encoding methods
• Encoder manipulates the state of the Writer
, using format-specific types and constructs in the Writer
• Writer will, based on the request, manipulate the underlying data structure (string builder, byte array output)
• Depending on the format-specific type, additional data may be written before or after the call-specific data.
◦ For example, in Protobuf, encodeInt
calls encodeTaggedInt
which actually write two ints to the underlying data structure; so a call to encodeInt(26)
would instead be translated to encodeInt(some tag) + encodeInt(26)
I understand the Reader/Writer as an Anti-Corruption Layer between Kotlin Serialization and whatever the underlying structure is. But isn't this last step also a serialization?
In order to encode this higher-level construct (tagged int, rather than primitive int) it still needs to determine the order of components (first Tag, then Value), and encode them in the data structure (int, int).
As an example, Protobuf could decide tomorrow that Tags are now Shorts, and Values are now little-endian. but technically, the outer most call from the Format doesn't know about that. It's still just going to boil down to encodeInt
calls from the Serializer's perspective, and the Encoder is going to translate those to encodeTaggedInt
calls, which gets translated to whatever the format needs.Zyle Moore
12/01/2024, 5:39 AMencodeInt
implementation doesn't write a signed int, but instead an unsigned int as an unquoted string. Here, it seems like the Encoder is the correct place to put format-specific primitive encoding decisions, going as far as having a separate encoder for format-specific representations of primitive data types.
However, the Serializable value itself can be represented differently with different Serializers. Some of the examples for a Color
type are "#fff"
, {"r":0.1,"g":1.0,"b":0.5}
, and red
. These Serializers would vary in their calls to the Encoder, depending on the serial representation they provide. The Json example above would start with a beginStructure
call probably, the string could be encodeString
or encodeInline
or repeated calls to encodeByte
or a custom encodeHex
, or an encodeInt
that actually calls an encodeQuotedHexString
.
So my question is, if format-specific data types require format-specific representations, where do we draw the line between the responsibility of format-specific decisions? How would I know to choose
override fun serialize(encoder: Encoder, value: NullTerminatedString) {
encoder.encodeString(value.string)
}
...
override fun encodeString(value: String) {
output.writeString(value)
output.writeByte(0)
}
over
override fun serialize(encoder: Encoder, value: NullTerminatedString) {
encoder.encodeString(value.string)
encoder.encodeByte(0)
}
...
override fun encodeString(value: String) {
output.writeString(value)
}
override fun encodeByte(value: Byte) {
output.writeByte(value)
}
Zyle Moore
12/01/2024, 6:46 PMZyle Moore
12/07/2024, 11:18 PMEncoder
interface actually has has a method encodeSerializableValue
which delegates encoding of that value to a different Serializer.