Skip to content

fix(kotlin): support oneOf with primitive types in kotlinx_serialization#22986

Open
pvcresin wants to merge 1 commit intoOpenAPITools:masterfrom
pvcresin:fix/kotlin-kotlinx-oneof-primitive-types
Open

fix(kotlin): support oneOf with primitive types in kotlinx_serialization#22986
pvcresin wants to merge 1 commit intoOpenAPITools:masterfrom
pvcresin:fix/kotlin-kotlinx-oneof-primitive-types

Conversation

@pvcresin
Copy link

@pvcresin pvcresin commented Feb 16, 2026

Description

The kotlin-client generator with jvm-retrofit2 library and kotlinx_serialization failed to generate working code for oneOf schemas containing primitive types (e.g. string, integer) without a discriminator. The existing template only handled discriminator-based oneOf via sealed interfaces, producing an empty class for primitive oneOf.

company_id:
  oneOf:
    - type: string
    - type: integer
      format: int64

Changes:

  • Add a non-discriminator fallback in oneof_class.mustache that generates a data class with actualInstance and a custom KSerializer, ported from the multiplatform template
  • Add isLong (int64) handling to the serialize branch, with an isInteger guard to avoid generating a dead branch for int64 types
  • Add test for oneOf primitive types with kotlinx_serialization

Before

@Serializable
class TestModelCompanyId () {
}

After

@Serializable(with = TestModelCompanyId.TestModelCompanyIdSerializer::class)
data class TestModelCompanyId(var actualInstance: Any? = null) {

    object TestModelCompanyIdSerializer : KSerializer<TestModelCompanyId> {
        override fun serialize(encoder: Encoder, value: TestModelCompanyId) {
            when (val instance = value.actualInstance) {
                is kotlin.String -> jsonEncoder.encodeString(instance)
                is kotlin.Long -> jsonEncoder.encodeLong(instance)
                null -> jsonEncoder.encodeJsonElement(JsonNull)
                else -> throw SerializationException(...)
            }
        }

        override fun deserialize(decoder: Decoder): TestModelCompanyId {
            // try each type in schema order
            try { decodeFromJsonElement<kotlin.String>(...) } catch { ... }
            try { decodeFromJsonElement<kotlin.Long>(...) } catch { ... }
        }
    }
}

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • File the PR against the correct branch: master (upcoming 7.x.0 minor release - breaking changes with fallbacks), 8.0.x (breaking changes without fallbacks)
  • If your PR solves a reported issue, reference it using GitHub's linking syntax (e.g., having "fixes #123" present in the PR description) (no existing issue)
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request. (cc: @karismann @Zomzog @andrewemery @4brunu @yutaka0m @stefankoppier @e5l @dennisameling)

Summary by cubic

Fixes Kotlin client generation with jvm-retrofit2 + kotlinx_serialization for oneOf schemas that include primitive types without a discriminator. Now generates a data-class wrapper with a custom KSerializer that correctly handles primitives and null, and tries each type on decode.

  • Bug Fixes
    • Added non-discriminator fallback in oneof_class.mustache: generate a data class with actualInstance and an in-file KSerializer (ported from multiplatform).
    • Serializer covers String, Boolean, Int, Long (int64 via isInteger guard), Float, Double, and null (JsonNull); delegates non-primitives to their serializers.
    • Added a test validating wrapper generation, try-each deserialization, and that the parent model references the wrapper type.

Written for commit cff7fbe. Summary will update on new commits.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/main/resources/kotlin-client/oneof_class.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/kotlin-client/oneof_class.mustache:217">
P2: oneOf deserialization returns on the first successful decode without checking for multiple matches, so ambiguous primitives (e.g., Int/Long/Double) will silently select the first schema instead of enforcing exactly one match.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@pvcresin
Copy link
Author

@cubic-dev-ai rerun a review

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Feb 17, 2026

@cubic-dev-ai rerun a review

@pvcresin I have started the AI code review. It will take a few minutes to complete.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/main/resources/kotlin-client/oneof_class.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/kotlin-client/oneof_class.mustache:198">
P2: `{{{dataType}}}.serializer()` will not compile for parameterized/collection oneOf types (e.g., `List<Foo>`/`Map<String,Bar>`), so the generated Kotlin for oneOf with array/map schemas will fail to compile. Use a collection serializer or the top‑level `serializer<T>()` instead.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@pvcresin
Copy link
Author

@cubic-dev-ai rerun a review

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Feb 17, 2026

@cubic-dev-ai rerun a review

@pvcresin I have started the AI code review. It will take a few minutes to complete.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/main/resources/kotlin-client/oneof_class.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/kotlin-client/oneof_class.mustache:217">
P2: Non-discriminator oneOf deserialization returns on the first successful decode, so overlapping primitive schemas can match multiple types but the first one wins, violating oneOf’s “exactly one” semantics.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@pvcresin
Copy link
Author

This is a PR regarding Kotlin. Could you please review it?

@karismann @Zomzog @andrewemery @4brunu @yutaka0m @stefankoppier @e5l @dennisameling

Copy link
Contributor

@e5l e5l left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @pvcresin, thank you for the PR!

lgtm

The kotlin-client generator with `jvm-retrofit2` library and
`kotlinx_serialization` failed to generate working code for oneOf
schemas containing primitive types (e.g. string, integer) without a
discriminator. The existing template only handled discriminator-based
oneOf via sealed interfaces, producing an empty class for primitive
oneOf.

Changes:
- Add a non-discriminator fallback in oneof_class.mustache that
  generates a data class with actualInstance and a custom KSerializer,
  ported from the multiplatform template
- Add isLong (int64) handling to the serialize branch, with an
  isInteger guard to avoid generating a dead branch for int64 types
- Add test for oneOf primitive types with kotlinx_serialization
@pvcresin pvcresin force-pushed the fix/kotlin-kotlinx-oneof-primitive-types branch from d673376 to cff7fbe Compare February 18, 2026 01:52
@pvcresin
Copy link
Author

pvcresin commented Feb 18, 2026

This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.

Rebased on the latest master. The code has not been changed since the PR was created.

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.

2 participants

Comments