CanonicalMapSerializer
KSerializer for a Map that emits entries in key order, so that — subject to the precondition below — two replicas at the same logical state produce identical bytes regardless of merge history or host platform.
The zoo's map-backed states are merged through MapMerge, which builds a HashMap, whose iteration order is unspecified and differs by platform in kind, not merely in detail: on the JVM it is hash-bucket order, so it tracks the key set and the map's capacity; on Kotlin/Native it is insertion order, so it tracks merge history directly. Neither is a function of the logical value alone, so the same GCounter could encode to different bytes on two replicas — or on one replica and its own peer (issue #1957).
Sort order: by the structural encoding of each key — every K is serialized to a sequence of primitive leaves and those sequences compared lexicographically. This works for data classes, inline value classes and compound keys, and is correct where a toString-based sort is not: seq 2 and 10 order numerically, not as text. See sortedByCanonicalKey, the one canonical order shared with the dot family (#752, #1964).
It is a total preorder, not a total order: two distinct keys whose leaf sequences are identical compare equal, and sortedWith is stable, so they retain their input order — for those keys the encoding is history-dependent again.
Precondition — this class canonicalises the key ORDER, nothing else. The bytes are canonical only if K and V each serialize canonically in their own right. Two traps:
A key or value reaching an unordered Set or Map field through a non-canonical serializer is not canonical, and neither is the whole. Two equal keys of a type like
data class Key(val tags: Set<String>)encode to different bytes.Values are passed through
vSerializeruntouched. AMap<String, GCounter>is canonical here only onceGCounteritself encodes canonically; wrapping the outer map is not sufficient.
K may be @Contextual or polymorphic. The sort reads the format's SerializersModule off the encoder and hands it to the internal leaf encoder, so a key resolves in the sort exactly as it resolves in the format (#2035). It contributes its class discriminator as an ordinary leaf, so polymorphic keys order by discriminator first and payload second.
Wire format is unchanged — the same map layout, with entries reordered.