3.7 KiB
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