Skip to content

fix: preserve Kotlin untagged-union member payloads - #176

Merged
adamcavendish merged 1 commit into
mainfrom
adamcavendish/kotlin-untagged-union-selection
Nov 12, 2026
Merged

adamcavendish merged 1 commit into
mainfrom
adamcavendish/kotlin-untagged-union-selection

Conversation

@adamcavendish

@adamcavendish adamcavendish commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Preserve Kotlin OkHttp untagged-union member payloads with enum-aware structural
selection and configurable typed or raw adapters.

Problem

The existing Gson adapter chooses object members by required keys alone. Members
with identical required keys but disjoint numeric enums therefore select the first
variant and silently discard fields from the actual matching member. The same
failure affects containing fields, lists, and maps.

Kotlin sealed subclasses also need serialization through their declared member
type: reflecting a nested union's runtime variant emits a value0 wrapper instead
of that union's wire payload.

Behavior

Policy Result
firstMatch Default: first structural match in schema order, before opaque fallbacks.
strict Reject multiple authored concrete matches before decoding one member.
raw Explicit VariantRaw(JsonElement) preserving known and unknown JSON.

Matching retains finite scalar constraints, nested arrays/maps, null permissions,
and productive recursion. Exact decimal significands and unbounded exponents avoid
floating-point rounding and matcher-only scale limits. Recursive checks cache both
successful and failed predicates by JSON-node identity.

Typed adapters delegate once to the selected original member. Nested strict and
native decode errors propagate without retrying another member or raw fallback.
Serialization uses the declared payload type to preserve nested union boundaries.
Raw adapters preserve null object entries and large numbers while restoring the
writer's previous null policy, including on failure.

Compatibility

  • Default and strict modes retain sealed classes, variant names, typed payloads,
    and existing root-null behavior (null or nullable-union VariantNull).
  • Kotlin typealias declarations remain transparent; intersections retain their
    existing wrapper fields rather than receiving a new allOf wire codec.
  • Ordinary models, enum declarations, tagged codecs, generated dependencies,
    shared IR, and other-language generators are unchanged.
  • Raw mode is opt-in through kotlin-okhttp.untagged_union_strategy; an enclosing
    VariantRaw model uses VariantRawVariant for its raw case.

Boundaries

This is the Kotlin-only union repair following merged Java #175. Dictionary-only
models, additional-property preservation, and multi-member allOf redesign remain
separate work. Structural selection is not full JSON Schema validation; format
bounds and existing native codec limits remain with the selected member's codec.

@codecov

codecov Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.57597% with 59 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/generators/kotlin/okhttp/matching.rs 85.55% 51 Missing ⚠️
src/generators/kotlin/okhttp/union.rs 95.16% 6 Missing ⚠️
src/generators/kotlin/okhttp/sigil_emit.rs 95.45% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

- Select Kotlin OkHttp union members using retained finite scalar and structural
  facts so unconstrained Gson fields cannot swallow another member's payload.
- Expose firstMatch, strict, and explicit raw policies while retaining typed
  sealed classes, data-class payloads, and existing root-null behavior by default.
- Count original concrete members before opaque fallbacks and decode the selected
  member once, preserving nested strict errors and transparent type aliases.
- Delegate serialization through each declared payload type so nested sealed
  unions do not leak their runtime Variant wrappers into the wire representation.
- Compare exact significands and unbounded exponents without rounding, adding
  matcher scale limits, or hiding existing native scalar and byte decoder errors.
- Cache successful and failed recursive predicates by JSON node identity with
  qualified Kotlin cache types that cannot collide with generated model names.
- Preserve raw JSON trees, including null entries and large numbers, and restore
  the writer's null policy even when native tree serialization fails.
- Emit adapters with structured declarations and semantic nested case types;
  share intersection wrapper keys between model emission and matching.
- Retain ordinary and tagged codecs, wrapped intersections, and untouched
  generated dependencies without changing shared IR or other-language backends.
- Exercise native model, recursive helper, and HTTP response/error consumers in
  isolated policy projects alongside exact render and Kotlin golden contracts.
@adamcavendish
adamcavendish force-pushed the adamcavendish/kotlin-untagged-union-selection branch from d63568c to 36cd171 Compare November 11, 2026 15:42
@adamcavendish
adamcavendish merged commit 9419ef1 into main Nov 12, 2026
31 checks passed
@adamcavendish
adamcavendish deleted the adamcavendish/kotlin-untagged-union-selection branch November 12, 2026 01:48
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.

1 participant