Skip to content

Fix discriminator values in writable type variants#3301

Merged
mrlubos merged 7 commits intomainfrom
copilot/fix-type-discriminator-values
Feb 3, 2026
Merged

Fix discriminator values in writable type variants#3301
mrlubos merged 7 commits intomainfrom
copilot/fix-type-discriminator-values

Conversation

Copy link
Contributor

Copilot AI commented Feb 3, 2026

Writable type variants generated from schemas with discriminators were using schema names ('DogPayloadWritable') instead of preserving the original discriminator values ('dog') from the OpenAPI spec, causing backend deserialization failures.

Changes

Fixed discriminator values in writable type variants:

  • Creates writable variants of parent schemas that have discriminators (e.g., AnimalPayloadWritable)
  • Updates discriminator mappings in parent variants to point to writable child refs
  • Updates $refs in writable children to point to the appropriate parent variant based on context (read/write)
  • Updates discriminator mappings in oneOf/anyOf contexts to point to appropriate read/write variant refs

Test coverage:

  • Added specs/3.1.x/discriminator-one-of-read-write.yaml demonstrating the issue with oneOf + discriminators + readOnly properties
  • Added snapshot test in packages/openapi-ts-tests/main/test/3.1.x.test.ts to validate the fix

Solution

When schemas with discriminators are split into read/write variants, parent schemas (like AnimalPayload) that define discriminator mappings are now also cloned into writable variants (like AnimalPayloadWritable). The discriminator mappings in these parent variants are updated to point to the appropriate child variants:

  • AnimalPayload (read) maps: dog -> DogPayload, cat -> CatPayload
  • AnimalPayloadWritable (write) maps: dog -> DogPayloadWritable, cat -> CatPayloadWritable

Writable child schemas reference the writable parent variant via allOf, ensuring they inherit the correct discriminator mappings.

Result

All discriminator values now correctly preserve the original OpenAPI spec values:

export type DogPayloadWritable = Omit<AnimalPayloadWritable, 'typeDiscriminator'> & {
  breed: string;
  canFetch: boolean;
  typeDiscriminator: 'dog';  // ✓ Correct - preserves spec value
};

export type CatPayloadWritable = Omit<AnimalPayloadWritable, 'typeDiscriminator'> & {
  breed: string;
  livesRemaining: number;
  typeDiscriminator: 'cat';  // ✓ Correct - preserves spec value
};

export type CreatePetRequestWritable = {
  name: string;
  animal: ({
    typeDiscriminator: 'dog';  // ✓ Correct in oneOf contexts
  } & DogPayloadWritable) | ({
    typeDiscriminator: 'cat';  // ✓ Correct in oneOf contexts
  } & CatPayloadWritable);
};
Original prompt

This section details on the original issue you should resolve

<issue_title>Write schemas generated for oneOf types override type discriminator values</issue_title>
<issue_description>### Description

When @hey-api/openapi-ts generates TypeScript types from an OpenAPI spec containing polymorphic types with readOnly properties, it creates "Writable" variants with incorrect typeDiscriminator values.

The generated types use a PascalCase discriminator derived from the type name (e.g., 'DogPayloadWritable') instead of preserving the discriminator value from the OpenAPI spec (e.g., 'dog').

This causes deserialization failures in the backend that still expects the original type discriminators it has defined.

Interestingly enough, this issue wasn't present in @hey-api/openapi-ts 0.84.4, which I suppose might have been due to writable types not being generated properly with polymorphic types so this bug never surfaced itself because of that.

Reproducible example or configuration

Environment

  • @hey-api/openapi-ts: v0.90.10
  • Backend: ASP.NET Core with System.Text.Json
  • OpenAPI generator: Swashbuckle

Reproduction

.NET models

[JsonPolymorphic(TypeDiscriminatorPropertyName = "typeDiscriminator")]
[JsonDerivedType(typeof(DogPayload), "dog")]
[JsonDerivedType(typeof(CatPayload), "cat")]
public abstract record AnimalPayload;

public record DogPayload : AnimalPayload
{
    public required string Breed { get; init; }
    public required bool CanFetch { get; init; }

    /// Computed property - marked as readOnly in OpenAPI schema.
    /// This triggers hey-api to generate a Writable variant.
    public string DisplayBreed => $"{Breed} (Dog)";
}

public record CatPayload : AnimalPayload
{
    public required string Breed { get; init; }
    public required int LivesRemaining { get; init; }

    /// Computed property - marked as readOnly in OpenAPI schema.
    /// This triggers hey-api to generate a Writable variant.
    public string DisplayBreed => $"{Breed} (Cat)";
}

public record CreatePetRequest
{
    public required string Name { get; init; }
    public required AnimalPayload Animal { get; init; }
}

Generated TS Read types (correct)

export type DogPayload = Omit<AnimalPayload, 'typeDiscriminator'> & {
  breed: string
  canFetch: boolean
  readonly displayBreed: string
  typeDiscriminator: 'dog'  // Correct - matches OpenAPI spec
}

export type CatPayload = Omit<AnimalPayload, 'typeDiscriminator'> & {
  breed: string
  livesRemaining: number
  readonly displayBreed: string
  typeDiscriminator: 'cat'  // Correct - matches OpenAPI spec
}

Generated TS Writable types (incorrect):

export type DogPayloadWritable = Omit<AnimalPayload, 'typeDiscriminator'> & {
  breed: string
  canFetch: boolean
  typeDiscriminator: 'DogPayloadWritable'  // should be 'dog'
}

export type CatPayloadWritable = Omit<AnimalPayload, 'typeDiscriminator'> & {
  breed: string
  livesRemaining: number
  typeDiscriminator: 'CatPayloadWritable'  // should be 'cat'
}

export type CreatePetRequestWritable = {
  name: string
  animal: DogPayloadWritable | CatPayloadWritable  // Uses wrong discriminators
}

OpenAPI specification (optional)

OpenAPI Schema (Backend)

components:
  schemas:
    AnimalPayload:
      type: object
      required: [typeDiscriminator]
      properties:
        typeDiscriminator:
          type: string
      discriminator:
        propertyName: typeDiscriminator
        mapping:
          dog: '#/components/schemas/DogPayload'
          cat: '#/components/schemas/CatPayload'

    DogPayload:
      allOf:
        - $ref: '#/components/schemas/AnimalPayload'
        - type: object
          required: [breed, canFetch, displayBreed]
          properties:
            breed:
              type: string
            canFetch:
              type: boolean
            displayBreed:
              type: string
              readOnly: true  # <-- This triggers Writable variant generation

    CatPayload:
      allOf:
        - $ref: '#/components/schemas/AnimalPayload'
        - type: object
          required: [breed, livesRemaining, displayBreed]
          properties:
            breed:
              type: string
            livesRemaining:
              type: integer
            displayBreed:
              type: string
              readOnly: true  # <-- This triggers Writable variant generation

    CreatePetRequest:
      type: object
      required: [name, animal]
      properties:
        name:
          type: string
        animal:
          oneOf:
            - $ref: '#/components/schemas/DogPayload'
            - $ref: '#/components/schemas/CatPayload'

System information (optional)

No response</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@bolt-new-by-stackblitz
Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@changeset-bot
Copy link

changeset-bot bot commented Feb 3, 2026

🦋 Changeset detected

Latest commit: 7e6945d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hey-api/shared Patch
@hey-api/openapi-python Patch
@hey-api/openapi-ts Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Co-authored-by: mrlubos <12529395+mrlubos@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix schemas generated for oneOf types to preserve discriminator values Fix discriminator values in oneOf writable type variants (partial) Feb 3, 2026
Copilot AI requested a review from mrlubos February 3, 2026 15:38
@vercel
Copy link

vercel bot commented Feb 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hey-api-docs Ready Ready Preview, Comment Feb 3, 2026 11:21pm

Request Review

@mrlubos mrlubos marked this pull request as ready for review February 3, 2026 21:12
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. bug 🔥 Broken or incorrect behavior. labels Feb 3, 2026
@codecov
Copy link

codecov bot commented Feb 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 34.89%. Comparing base (5ff5370) to head (7e6945d).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3301   +/-   ##
=======================================
  Coverage   34.89%   34.89%           
=======================================
  Files         401      401           
  Lines       22244    22244           
  Branches     1429     1429           
=======================================
  Hits         7763     7763           
  Misses      14477    14477           
  Partials        4        4           
Flag Coverage Δ
unittests 34.89% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 3, 2026

Open in StackBlitz

@hey-api/codegen-core

npm i https://pkg.pr.new/hey-api/openapi-ts/@hey-api/codegen-core@3301

@hey-api/nuxt

npm i https://pkg.pr.new/hey-api/openapi-ts/@hey-api/nuxt@3301

@hey-api/openapi-ts

npm i https://pkg.pr.new/hey-api/openapi-ts/@hey-api/openapi-ts@3301

@hey-api/shared

npm i https://pkg.pr.new/hey-api/openapi-ts/@hey-api/shared@3301

@hey-api/types

npm i https://pkg.pr.new/hey-api/openapi-ts/@hey-api/types@3301

@hey-api/vite-plugin

npm i https://pkg.pr.new/hey-api/openapi-ts/@hey-api/vite-plugin@3301

commit: 7e6945d

@mrlubos
Copy link
Member

mrlubos commented Feb 3, 2026

@copilot you need to add an actual snapshot test using the spec you added in this pull request so we can verify the fix works

Co-authored-by: mrlubos <12529395+mrlubos@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 3, 2026

@copilot you need to add an actual snapshot test using the spec you added in this pull request so we can verify the fix works

Added snapshot test for the new spec in commit 0b6a4fd. The test validates the current behavior where discriminator values in oneOf contexts are correct, while documenting the remaining issue with allOf inheritance.

@mrlubos
Copy link
Member

mrlubos commented Feb 3, 2026

@copilot have a look at the "Generated TS Writable types (incorrect):" section in the original issue and make sure you actually address the problems. So far, you've only confirmed we can reproduce the issue

Co-authored-by: mrlubos <12529395+mrlubos@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 3, 2026

@copilot have a look at the "Generated TS Writable types (incorrect):" section in the original issue and make sure you actually address the problems. So far, you've only confirmed we can reproduce the...

Fixed in commit 8126ad1. Writable types now correctly preserve discriminator values from the OpenAPI spec (e.g., 'dog' and 'cat') instead of using schema names (e.g., 'DogPayloadWritable'). The solution creates writable variants of parent schemas with updated discriminator mappings and updates child schemas to reference the appropriate parent variant.

Copilot AI changed the title Fix discriminator values in oneOf writable type variants (partial) Fix discriminator values in writable type variants Feb 3, 2026
@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Feb 3, 2026
@mrlubos mrlubos merged commit a8d0bf7 into main Feb 3, 2026
11 checks passed
@mrlubos mrlubos deleted the copilot/fix-type-discriminator-values branch February 3, 2026 23:32
@hey-api hey-api bot mentioned this pull request Feb 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug 🔥 Broken or incorrect behavior. lgtm This PR has been approved by a maintainer size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Write schemas generated for oneOf types override type discriminator values

2 participants