Skip to content

kotlinx-serialization: implement union support in KotlinXRenderer (sealed classes are emitted without serializers) #2951

Description

@schani

Summary

The Kotlin kotlinx-serialization renderer (--framework kotlinx) does not implement union serialization. KotlinXRenderer.ts opens with:

TODO: Union, Any, Top Level Array, Top Level Map

The base Kotlin renderer emits unions as a sealed class with wrapper members:

sealed class BarUnion {
    class DateTimeValue(val value: OffsetDateTime) : BarUnion()
    class EnumValue(val value: Bar)                : BarUnion()
}

Klaxon and Jackson make this work with hand-written converters that inspect the raw JSON and pick the matching member. The kotlinx renderer emits only the bare @Serializable sealed class — and kotlinx-serialization's default handling for sealed classes is discriminated polymorphism, expecting {"type": ..., ...} objects. The actual JSON for a quicktype union is the bare value itself (e.g. "quux" or 3.14), so decoding throws at runtime and encoding produces the wrong shape.

Impact

  • The kotlinx fixture added in Add fixture coverage for Kotlin kotlinx-serialization #2950 skips 20 JSON inputs and all union schemas because of this.
  • feat(kotlin): Support for date and datetime from JSON schema #2845 (Kotlin date/date-time support) makes it more visible: e.g. in test/inputs/schema/date-time-or-string.schema, the property bar: oneOf [date-time, enum] used to collapse to plain String (no date-time type ⇒ string absorbed the union) but now becomes a genuine date-time | enum union, so date-time.schema and date-time-or-string.schema also have to stay skipped for kotlinx.
  • keyword-unions.schema is additionally blocked by a synthesized-constructor >255-parameter JVM limit.

Proposed fix

Emit a custom KSerializer<XUnion> per union that dispatches on JSON shape (JsonPrimitive/string/number/boolean/JsonObject/JsonArray, with isString etc. to disambiguate), mirroring the member-matching logic (and match priority) that the klaxon/jackson converters use, and register it via @Serializable(with = ...) on the sealed class. The @file:UseSerializers + same-file serializer pattern introduced for date/time serializers in #2845 can be extended for this.

Acceptance: lift the union-related skips in test/languages.ts for KotlinXLanguage (JSON union inputs, union.schema et al., date-time.schema, date-time-or-string.schema) and claim the union feature so the .fail.union.json samples run.

Related gaps (same TODO line, possibly separate follow-ups)

  • Top-level arrays: rendered as typealias TopLevel = JsonArray<T>, which does not compile (JsonArray takes no type parameters) — currently 40 JSON inputs + union.schema skipped, and 77392.json/b4865.json in misc.
  • Any/top-level map handling.

Refs: #2950 (kotlinx fixture coverage, where the skips live), #2845 (Kotlin date-time support + kotlinx KSerializer pattern).

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions