Capítulo 29 de 61

Chapter 29: Defaults, Prefaults & Catch

Core Idea

Three distinct fallback mechanisms handle "missing or invalid input" differently: .default() short-circuits on undefined with an output-typed value, .prefault() substitutes an input-typed value and still runs the full parse, and .catch() supplies a fallback for any validation error (not just missing input).

Key Concepts

  • .default(value): if input is undefined, immediately returns value — parsing is short-circuited, so the default must match the schema's output type (post-transform).
  • .default(fn): a function re-invoked each time a default is needed (e.g. Math.random), instead of a fixed value.
  • .prefault(value): if input is undefined, substitutes value and still runs the full parsing pipeline (transforms, refinements) on it — so the prefault must match the schema's input type, not output.
  • .catch(value) / .catch(fn): returns a fallback when validation fails for any reason (not just missing input); the function form receives a context with the caught error/issues.

Code Examples

// default: short-circuits, must match OUTPUT type
const a = z.string().transform(val => val.length).default(0);
a.parse(undefined); // => 0 (the raw number 0, transform never runs)

// prefault: substitutes INPUT, still runs the pipeline
const b = z.string().transform(val => val.length).prefault("tuna");
b.parse(undefined); // => 4 ("tuna" is parsed and transformed)

// prefault lets mutating refinements still apply
const c = z.string().trim().toUpperCase().prefault("  tuna  ");
c.parse(undefined); // => "TUNA"

const d = z.string().trim().toUpperCase().default("  tuna  ");
d.parse(undefined); // => "  tuna  " (default bypasses trim/uppercase)

// catch: fallback on ANY validation error, not just missing input
const numberWithCatch = z.number().catch(42);
numberWithCatch.parse(5);      // => 5
numberWithCatch.parse("tuna"); // => 42 (validation failed, fallback used)
  • What it demonstrates: the difference between a default bypassing the pipeline versus a prefault running through it, and catch handling errors beyond just missing values.

Anti-patterns

  • Using .default() on a schema with a transform, expecting the transform to run on the default: it won't — .default() short-circuits before the transform. Use .prefault() if the default value should go through the same pipeline as real input.

Key Takeaways

  1. .default() is for "value is absent" and skips further processing; .prefault() is for "value is absent, but still validate/transform it like real input would be."
  2. .catch() is broader than both — it recovers from any failed parse, not just an undefined input.
  3. The type constraint differs accordingly: .default()'s value must match the output type, .prefault()'s must match the input type.

Connects To

  • Optional, Exact Optional, Nullable & Nullish: for allowing absence without supplying a fallback value at all.
  • Transforms: the pipeline that .prefault() runs through but .default() bypasses.