- Published on
How to dynamically generate Zod Schemas | Zod Validation Guide
- Authors
- Name
- Ripal & Zalak
How to dynamically generate Zod Schemas in TypeScript
The Problem
Suppose you have a reusable form component in a React app built with react-hook-form
. The form is defined dynamically, accepting an array of field configurations. You want to:
- Parse form inputs and validate them according to dynamic specifications.
- Infer types for better developer experience during development.
Example:
// Manually coded schema
const schema = z.object({
foo: z.string(),
bar: z.number(),
})
type Fields = z.infer<typeof schema> // { foo: string; bar: number; }
// Dynamic schema configuration
const fields = [
{ name: 'foo', fieldType: z.string() },
{ name: 'bar', fieldType: z.number() },
]
const generateSchemaFromFields = (fields: FieldConfig[]) => {
// Implementation needed
}
const generatedSchema = generateSchemaFromFields(fields)
The Solution
We can dynamically create a Zod schema using the field configurations. Here's a step-by-step solution:
Step 1: Define Field Configurations
Define the field configuration that includes the field name and validation rules:
const fields = [
{ name: 'foo', fieldType: z.string() },
{ name: 'bar', fieldType: z.number() },
] as { name: string; fieldType: z.ZodSchema }[]
Step 2: Generate Schema Dynamically
Convert the fields into a Zod schema object:
const generateSchemaFromFields = (fields: { name: string; fieldType: z.ZodSchema }[]) => {
const schemaObject = Object.fromEntries(fields.map((field) => [field.name, field.fieldType]))
return z.object(schemaObject)
}
const schema = generateSchemaFromFields(fields)
// Test the schema
const validInput = { foo: 'Hello', bar: 42 }
console.log(schema.safeParse(validInput).success) // true
const invalidInput = { foo: 42, bar: 'Oops' }
console.log(schema.safeParse(invalidInput).success) // false
Step 3: Handle Validation Rules Dynamically
Extend the solution to handle validation rules, such as min
or max
for strings or numbers:
function generateZodSchema(fields: { name: string; fieldType: string; validation?: any[] }[]) {
const schema: Record<string, z.ZodType<any>> = {}
fields.forEach((field) => {
let fieldSchema: z.ZodType<any>
switch (field.fieldType) {
case 'string':
fieldSchema = z.string()
break
case 'number':
fieldSchema = z.number()
break
default:
throw new Error(`Unsupported field type: ${field.fieldType}`)
}
// Apply validation rules
field.validation?.forEach((rule) => {
switch (rule.type) {
case 'min':
fieldSchema = fieldSchema.min(rule.value, rule.message)
break
case 'max':
fieldSchema = fieldSchema.max(rule.value, rule.message)
break
default:
throw new Error(`Unsupported validation type: ${rule.type}`)
}
})
schema[field.name] = fieldSchema
})
return z.object(schema)
}
const dynamicFields = [
{
name: 'username',
fieldType: 'string',
validation: [{ type: 'min', value: 3, message: 'Must be at least 3 characters' }],
},
{
name: 'age',
fieldType: 'number',
validation: [{ type: 'min', value: 18, message: 'Must be at least 18' }],
},
]
const dynamicSchema = generateZodSchema(dynamicFields)
console.log(dynamicSchema.safeParse({ username: 'John', age: 25 }).success) // true
console.log(dynamicSchema.safeParse({ username: 'Jo', age: 17 }).success) // false
FAQs
Q: Why use Zod for schema generation?
Zod provides an intuitive, composable API for schema validation. It integrates seamlessly with react-hook-form
using zodResolver
.
Q: Can this handle nested schemas?
Yes, you can extend the generateZodSchema
function to support nested field configurations by recursively creating schemas.
Q: Is TypeScript inference supported?
Yes, Zod allows you to infer TypeScript types using z.infer<typeof schema>
, which ensures type safety in your application.