Skip to content

Discriminated unions fail when including schemas with additionalProperties #92

@jvu009

Description

@jvu009

Issue

Discriminated unions fail when including schemas with additionalProperties

Problem Description

When generating Zod schemas from OpenAPI specs that include discriminated unions with schemas that have additionalProperties: true, the generated code fails with the error:

Error: A discriminator value for key kind could not be extracted from all schema options

This occurs because Zod's discriminatedUnion requires literal types for the discriminator property, but schemas with additionalProperties: true have a generic string type for their discriminator.

Example

Given this OpenAPI schema:

{
  "components": {
    "schemas": {
      "KnownStrategy": {
        "type": "object",
        "required": ["kind"],
        "properties": {
          "kind": {
            "type": "string",
            "enum": ["KNOWN"]
          },
          "data": {
            "type": "string"
          }
        }
      },
      "UnknownStrategy": {
        "type": "object",
        "required": ["kind"],
        "properties": {
          "kind": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "Strategy": {
        "oneOf": [
          { "$ref": "#/components/schemas/KnownStrategy" },
          { "$ref": "#/components/schemas/UnknownStrategy" }
        ],
        "discriminator": {
          "propertyName": "kind"
        }
      }
    }
  }
}

The current generator produces:

export const KnownStrategySchema = z.object({
  kind: z.literal('KNOWN'),
  data: z.string()
});

export const UnknownStrategySchema = z.object({
  kind: z.string()
}).catchall(z.any());

export const StrategySchema = z.discriminatedUnion('kind', [
  KnownStrategySchema,
  UnknownStrategySchema  // This causes the error because kind is not a literal
]);

Expected Behavior

The generator should handle unknown strategies (those with additionalProperties: true) differently by:

  1. Detecting schemas with additionalProperties: true
  2. For discriminated unions that include unknown strategies:
    • Create a union of:
      • A discriminated union containing only known strategies
      • Unknown strategies as separate union members

Example of expected output:

export const StrategySchema = z.union([
  z.discriminatedUnion('kind', [
    KnownStrategySchema  // Only known strategies in the discriminated union
  ]),
  UnknownStrategySchema  // Unknown strategies as a separate union member
]);

Use Case

This is particularly important for API designs that support plugin/extension patterns where:

  1. Core functionality uses known strategy types with strict validation
  2. Plugins/extensions can add new strategy types without modifying the core schema
  3. The API needs to handle both known and unknown strategies

Impact

  • Current workaround is to remove unknown strategies from the schema, which breaks the plugin/extension pattern
  • Forces API designers to choose between type safety and extensibility
  • Makes it difficult to implement manifest-based strategy registration

Proposed Solution

Modify the schema generator to:

  1. Detect schemas with additionalProperties: true
  2. Split union members into known and unknown strategies
  3. Generate appropriate Zod schemas based on the presence of unknown strategies

This would allow the API to maintain type safety for known strategies while supporting extensibility through unknown strategies.

Additional Context

  • This is a common pattern in plugin architectures
  • The OpenAPI spec correctly supports this through additionalProperties
  • The issue is specifically with how the Zod schema generator handles these cases

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions