Skip to content

fix: emit an empty-list marker so query encoding round-trips empty lists - #143

Merged
OmarAlJarrah merged 5 commits into
mainfrom
c3-emptylist-default
Jul 19, 2026
Merged

fix: emit an empty-list marker so query encoding round-trips empty lists#143
OmarAlJarrah merged 5 commits into
mainfrom
c3-emptylist-default

Conversation

@OmarAlJarrah

Copy link
Copy Markdown
Member

Summary

  • QueryEncoder.beginCollection emitted nothing at all for a list property with zero elements, which made "field present but empty" indistinguishable on the wire from "field absent."
  • QueryDecoder then treated the missing element as absent and fell back to the property's declared default, so encode(Foo(tags = emptyList())) followed by decode silently returned the non-empty default instead of an empty list.
  • Fixed by having the encoder emit a name[] marker pair whenever a list encodes to zero elements, and having the decoder recognize that marker (scoped to list-kind elements only) as "present, zero elements" instead of falling back to the default. Non-empty lists are untouched — their repeated name=value pairs already establish presence.

Test plan

  • Added a regression test reproducing the issue's exact repro (Foo(tags: List<String> = listOf("x")), encode with tags = emptyList(), decode, assert emptyList() not listOf("x"))
  • Added tests confirming a genuinely-absent field still falls back to its default, a list left at its declared empty default is still omitted from the encoded output (no spurious marker), and a non-empty list still round-trips without the marker
  • ./gradlew :kuri-serde-kotlinx:jvmTest :kuri-serde-kotlinx:ktlintCheck :kuri-serde-kotlinx:detekt :kuri-serde-kotlinx:apiCheck — all green
  • ./gradlew :kuri-serde-kotlinx:jsNodeTest — green (commonMain touched)
  • No public API surface changed (new symbols are internal/private), so no apiDump needed — confirmed by apiCheck passing unmodified

Closes #86

QueryEncoder previously emitted nothing for a list property with zero
elements, which is indistinguishable on the wire from the property being
absent. QueryDecoder then treated it as absent and fell back to the
property's declared default, so encoding an explicitly-empty list against
a non-empty default and decoding it back did not reproduce the empty list.

Emit a name[] marker pair when a list encodes to zero elements, and have
the decoder treat that marker as "present, zero elements" instead of
falling back to the default. Non-empty lists are unaffected since their
repeated name=value pairs already establish presence.

Closes #86
…tlin/Native

Kotlin/Native rejects a comma in a backtick-quoted identifier ("Name
contains illegal characters"), so the empty-list round-trip test failed to
compile on the Apple/native targets even though the JVM test suite accepted
the name and passed. Reword the name to remove the comma; the behavior it
covers is unchanged.
decodeNotNullMark() only checked params.has(currentName), so a nullable
List property carried solely by its tags[] empty-list marker (no tags=...
pairs) looked absent to kotlinx's NullableSerializer, which short-circuited
straight to null before ever reaching the QueryListDecoder that would have
produced emptyList(). Track whether the current element matched via the
marker and have decodeNotNullMark() report it as present too, so decoding
continues into beginStructure as it already does for non-nullable lists.
…s collision KDoc

beginCollection emitted the tags[] marker for any zero-size collection,
while the decode side only recognizes it for List elements; align the
encoder with a StructureKind.LIST guard so the two stay symmetric (the
format doesn't support Map-typed properties today, so this is hardening
rather than a live bug).

Also corrects the marker's KDoc: it can't collide with a property's
default serial name, but a property whose serial name is overridden via
@SerialName to end in [] could still collide - that shape just isn't
supported or tested.
@OmarAlJarrah
OmarAlJarrah merged commit 246a2a1 into main Jul 19, 2026
13 checks passed
@OmarAlJarrah
OmarAlJarrah deleted the c3-emptylist-default branch July 19, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kuri-serde-kotlinx: encoding an explicitly-empty list loses the value when the field has a non-empty default

1 participant