---
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)
