Discriminated Unions in Pydantic with Literal addresses a recurring problem in Python projects: Validate alternative payload shapes through one field that unambiguously identifies each variant. This guide explains the mechanism, provides an executable example, and identifies the boundaries that keep an implementation reliable.
Concept and use case
A plain union tries models in sequence; a discriminated union reads a tag and selects the matching model directly. This improves errors and OpenAPI schema.
For the related fundamentals, also read the Pydantic guide. Integration stays simpler when functions receive dependencies and data explicitly instead of relying on global state.
Practical example
from typing import Annotated, Literal
from pydantic import BaseModel, Field
class Card(BaseModel):
kind: Literal["card"]
last_four: str
class Pix(BaseModel):
kind: Literal["pix"]
key: str
Payment = Annotated[Card | Pix, Field(discriminator="kind")]
class Order(BaseModel):
payment: Payment
Use a stable key such as kind and one exclusive Literal per model. Never reuse a tag value across variants.
Important decisions
The correct choice depends on the public contract, expected volume, and failure behavior.
Consider concurrency, empty inputs, and partial failures. Document every limit that affects consumers and choose names that express intent.
Common mistakes
A minimal example does not replace bounds, error handling, and observability. Without a discriminator, errors may contain failures from every model. Changing a published tag breaks clients and requires a compatibility plan.
Avoid catching exceptions without context or returning partial output as if it were complete. An explicit failure is usually safer than silently incorrect data.
How to validate
Validate behavior, not only the happy path. Test each variant, unknown and missing tags, and fields from the wrong variant. Inspect the generated JSON Schema too.