Recursive Types
Zod supports recursive schemas, which are useful for validating nested data structures.
Basic Recursion
Use z.lazy()
to create recursive schemas:
ts
import { z } from "zod";
// Define a recursive category structure
type Category = {
name: string;
subcategories: Category[];
};
const Category: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
subcategories: z.array(Category),
})
);
// Validation
Category.parse({
name: "Food",
subcategories: [
{
name: "Fruits",
subcategories: [
{
name: "Tropical Fruits",
subcategories: [],
},
],
},
],
}); // passes
Mutually Recursive Types
You can define multiple types that are mutually recursive:
ts
// File system example
const FileSystem = z.lazy(() =>
z.object({
type: z.literal("directory"),
children: z.array(z.union([File, FileSystem])),
})
);
const File = z.object({
type: z.literal("file"),
content: z.string(),
});
// Validation
FileSystem.parse({
type: "directory",
children: [
{
type: "file",
content: "hello.txt",
},
{
type: "directory",
children: [
{
type: "file",
content: "world.txt",
},
],
},
],
}); // passes
JSON Type
Define JSON data structure using recursive types:
ts
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
type Literal = z.infer<typeof literalSchema>;
const JsonSchema: z.ZodType<Json> = z.lazy(() =>
z.union([
literalSchema,
z.array(JsonSchema),
z.record(JsonSchema),
])
);
type Json = Literal | { [key: string]: Json } | Json[];
// Validation
JsonSchema.parse({
string: "hello",
number: 42,
array: [1, 2, 3],
object: {
nested: {
data: [true, null, "world"],
},
},
}); // passes
Performance Considerations
Recursive types can impact validation performance, especially for deeply nested data:
ts
// Add maximum depth limit
const LimitedCategory = z.lazy(() =>
z.object({
name: z.string(),
subcategories: z.array(LimitedCategory).max(5), // limit number of subcategories
})
);
// Add maximum recursion depth
let depth = 0;
const MaxDepthCategory = z.lazy(() => {
if (depth > 3) throw new Error("Maximum recursion depth exceeded");
depth++;
return z.object({
name: z.string(),
subcategories: z.array(MaxDepthCategory),
});
});
Type Inference
TypeScript can correctly infer recursive types:
ts
const Comment = z.lazy(() =>
z.object({
text: z.string(),
replies: z.array(Comment),
})
);
type Comment = z.infer<typeof Comment>;
// {
// text: string;
// replies: Comment[];
// }
// Using the type
const comment: Comment = {
text: "Parent comment",
replies: [
{
text: "Child comment",
replies: [],
},
],
};