Files
gruperly/.agents/skills/zod/references/refine-catch.md
2026-09-04 16:49:24 -03:00

3.7 KiB

title, impact, impactDescription, tags
title impact impactDescription tags
Use catch() for Fault-Tolerant Parsing MEDIUM parse() fails on first invalid field; catch() provides fallback values, enabling partial success with degraded data refine, catch, fallback, resilience

Use catch() for Fault-Tolerant Parsing

When parsing data that might have some invalid fields but you want to accept what's valid, use .catch() to provide fallback values instead of failing entirely. This enables graceful degradation for partially corrupted data.

Incorrect (all-or-nothing parsing):

import { z } from 'zod'

const userPrefsSchema = z.object({
  theme: z.enum(['light', 'dark']),
  fontSize: z.number().min(8).max(32),
  language: z.string(),
  notifications: z.boolean(),
})

// Corrupted localStorage data
const stored = {
  theme: 'invalid-theme',  // Bad
  fontSize: 200,  // Bad
  language: 'en',  // Good
  notifications: 'yes',  // Bad - should be boolean
}

userPrefsSchema.parse(stored)
// ZodError: Invalid enum value at "theme"
// User loses ALL their preferences because one field is bad

Correct (fault-tolerant with catch):

import { z } from 'zod'

const userPrefsSchema = z.object({
  theme: z.enum(['light', 'dark']).catch('light'),
  fontSize: z.number().min(8).max(32).catch(16),
  language: z.string().catch('en'),
  notifications: z.boolean().catch(true),
})

// Corrupted data
const stored = {
  theme: 'invalid-theme',
  fontSize: 200,
  language: 'en',
  notifications: 'yes',
}

const prefs = userPrefsSchema.parse(stored)
// {
//   theme: 'light',      // Fallback used
//   fontSize: 16,        // Fallback used
//   language: 'en',      // Original value preserved
//   notifications: true  // Fallback used
// }
// User gets mostly working preferences instead of error

Catch with factory function:

// Factory function receives the caught error
const schema = z.object({
  data: z.array(z.number()).catch((ctx) => {
    console.warn('Invalid data array:', ctx.error)
    return []  // Return empty array as fallback
  }),
})

Use case: API response resilience:

const productSchema = z.object({
  id: z.string(),
  name: z.string(),
  price: z.number().positive(),
  // Legacy field that might be missing or wrong format
  legacyCode: z.string().catch('UNKNOWN'),
  // External data that might be malformed
  metadata: z.record(z.string()).catch({}),
})

// API returns partial data
const apiResponse = {
  id: 'prod-123',
  name: 'Widget',
  price: 29.99,
  legacyCode: null,  // Bad - should be string
  metadata: 'invalid',  // Bad - should be object
}

const product = productSchema.parse(apiResponse)
// Works! Returns product with fallbacks for bad fields

Difference between catch() and default():

// .default() - only fills in undefined
z.string().default('fallback')
// undefined -> 'fallback'
// null -> ZodError
// '' -> '' (empty string is valid)

// .catch() - fallback for ANY parse failure
z.string().catch('fallback')
// undefined -> 'fallback'
// null -> 'fallback'
// 123 -> 'fallback'
// Even valid strings pass through unchanged

Combining catch with validation:

// Catch only specific validation failures
const schema = z.string()
  .email()
  .catch('invalid@example.com')  // Fallback if not valid email

// Chain for complex defaults
const ageSchema = z.coerce.number()
  .int()
  .min(0)
  .max(120)
  .catch(0)  // Invalid ages become 0

When NOT to use this pattern:

  • When invalid data should cause errors (strict validation)
  • When you need to know which fields failed (use safeParse)
  • Critical fields that must be valid

Reference: Zod API - catch