- Published on
Conditional Validation in Zod: A Comprehensive Guide
- Authors
- Name
- Ripal & Zalak
Introduction
Zod is a powerful TypeScript-first schema validation library that allows developers to define schemas for data validation. A common requirement is implementing conditional validation—where certain fields in an object are required or optional depending on the value of another field. This blog provides a step-by-step guide to implementing conditional validation in Zod with practical examples.
Use Case
Consider a scenario where you're validating an array of invoice items. Each item is an object with various properties. The requirement is:
- If the
id
property is set, all other fields should be optional (indicating an update). - If the
id
property is not set, all other fields should be required (indicating a new item).
Implementing Conditional Validation in Zod
Step 1: Define the Base Schema
Create a base schema for your items with all fields.
import { z } from 'zod'
const baseItemSchema = z.object({
id: z.number().positive().optional(),
articleId: z.number().positive(),
sku: z.number().positive(),
description: z.string().nonempty().max(1000),
quantity: z.number().positive(),
price: z.number().positive(),
term: z.enum(['month', 'year', 'flat', 'piece']),
})
superRefine
Step 2: Add Conditional Validation Using Use the superRefine
method to enforce conditional validation logic.
const conditionalItemSchema = baseItemSchema.superRefine((data, context) => {
if (data.id !== undefined) {
// If `id` is defined, other fields are optional.
return
} else {
// If `id` is not defined, ensure all other fields are provided.
const requiredFields = ['articleId', 'sku', 'description', 'quantity', 'price', 'term']
requiredFields.forEach((field) => {
if (data[field] === undefined || data[field] === null) {
context.addIssue({
code: z.ZodIssueCode.custom,
message: `${field} is required when 'id' is not set`,
path: [field],
})
}
})
}
})
Step 3: Define the Array Schema
Wrap the conditionalItemSchema
in a Zod array schema to validate multiple items.
const itemsSchema = z.array(conditionalItemSchema)
Step 4: Validate Data
Test the schema with different inputs.
Valid Data Example (Update Scenario)
const validUpdate = [
{
id: 1,
articleId: undefined,
sku: undefined,
description: undefined,
quantity: undefined,
price: undefined,
term: undefined,
},
]
console.log(itemsSchema.safeParse(validUpdate).success) // true
Invalid Data Example (New Item Scenario)
const invalidNewItem = [
{
articleId: 123,
sku: undefined, // Missing required field
description: 'Product Description',
quantity: 5,
price: 100,
term: 'month',
},
]
console.log(itemsSchema.safeParse(invalidNewItem).success) // false
Advanced Techniques
refine
for Simpler Cases
Using For simpler conditional validation scenarios, you can use refine
with custom conditions.
const schema = z
.object({
type: z.enum(['business', 'personal']),
abn: z.string().optional(),
})
.refine(
(data) => {
return data.type === 'business' ? !!data.abn : true
},
{
message: 'ABN is required for business type',
}
)
Leveraging Utility Functions
To avoid repetitive code, create a utility function for conditional validation logic.
function requireFieldsWhenIdAbsent(fields: string[], data: any, context: z.RefinementCtx) {
fields.forEach((field) => {
if (data[field] === undefined) {
context.addIssue({
code: z.ZodIssueCode.custom,
message: `${field} is required when 'id' is not set`,
path: [field],
})
}
})
}
Conclusion
Conditional validation in Zod allows you to enforce complex business rules while keeping your code clean and maintainable. By combining superRefine
and modular utility functions, you can handle a wide range of validation scenarios with ease.
Have insights or additional tips? Share them in the comments below!