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

144 lines
3.7 KiB
Markdown

---
title: Use catch() for Fault-Tolerant Parsing
impact: MEDIUM
impactDescription: parse() fails on first invalid field; catch() provides fallback values, enabling partial success with degraded data
tags: 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):**
```typescript
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):**
```typescript
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:**
```typescript
// 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:**
```typescript
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():**
```typescript
// .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:**
```typescript
// 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](https://zod.dev/api#catch)