Capítulo 20 de 61

Chapter 20: Discriminated Unions & Intersections

Core Idea

z.discriminatedUnion() is a faster, more precise union for the common case where every option is an object sharing one literal "tag" field; z.intersection() represents a logical AND of two schemas.

Key Concepts

  • z.discriminatedUnion(key, [...objectSchemas]): each option must be an object schema whose key property is a literal (or z.enum()/z.literal()/z.null()/z.undefined()) — Zod uses the discriminator value to jump straight to the matching option instead of trying each one in order, which is both faster and gives better error messages than a naive z.union().
  • Nesting: discriminated unions can nest — an option can itself be a discriminated union on a different key, and Zod resolves the optimal lookup strategy across levels.
  • z.intersection(a, b): the parsed value must satisfy both schemas simultaneously; for primitive unions this narrows to their common members (e.g. intersecting number | string with number | boolean infers just number).

Code Examples

const MyResult = z.discriminatedUnion("status", [
  z.object({ status: z.literal("success"), data: z.string() }),
  z.object({ status: z.literal("failed"), error: z.string() }),
]);

// nested discriminated unions
const MyErrors = z.discriminatedUnion("code", [
  z.object({ status: z.literal("failed"), code: z.literal(400) }),
  z.object({ status: z.literal("failed"), code: z.literal(401) }),
]);
const Combined = z.discriminatedUnion("status", [
  z.object({ status: z.literal("success"), data: z.string() }),
  MyErrors,
]);

const a = z.union([z.number(), z.string()]);
const b = z.union([z.number(), z.boolean()]);
const c = z.intersection(a, b); // inferred type: number
  • What it demonstrates: a discriminant-based union that narrows both parsing strategy and TypeScript type, plus combining two schemas' constraints via intersection.

Anti-patterns

  • Using z.union() for a large set of object variants with a shared tag field: it's slower (checks every option in order) and gives worse error messages than z.discriminatedUnion(), which can jump straight to the right branch.

Key Takeaways

  1. Whenever every union option is an object sharing one literal-valued key, use z.discriminatedUnion() — it's strictly better than z.union() for that shape.
  2. Discriminated unions nest cleanly; Zod figures out the combined lookup strategy automatically.
  3. z.intersection() is for combining independent constraints (AND), not for merging object shapes — use .extend() or object spread for that instead.

Connects To

  • Unions & Exclusive Unions: the general-purpose union this specializes for the common "tagged object" case.
  • Objects — Extending & Deriving: object spread as the idiomatic way to merge object shapes, as opposed to intersecting arbitrary schemas.