-
-
Notifications
You must be signed in to change notification settings - Fork 306
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