Recursive types
You can define a recursive schema in Zod, but because of a limitation of TypeScript, their type can't be statically inferred. Instead you'll need to define the type definition manually, and provide it to Zod as a "type hint".
const baseCategorySchema = z.object({
name: z.string()
});
type Category = z.infer<typeof baseCategorySchema> & {
subcategories: Category[];
};
const categorySchema: z.ZodType<Category> = baseCategorySchema.extend({
subcategories: z.lazy(() => categorySchema.array())
});
categorySchema.parse({
name: "People",
subcategories: [
{
name: "Politicians",
subcategories: [
{
name: "Presidents",
subcategories: []
}
]
}
]
}); // passes
Thanks to crasite for this example.
ZodType with ZodEffects
When using z.ZodType
with z.ZodEffects
(
.refine
,
.transform
,
preprocess
,
etc...
), you will need to define the input and output types of the schema. z.ZodType<Output, z.ZodTypeDef, Input>
const isValidId = (id: string): id is `${string}/${string}` => id.split("/").length === 2;
const baseSchema = z.object({
id: z.string().refine(isValidId)
});
type Input = z.input<typeof baseSchema> & {
children: Input[];
};
type Output = z.output<typeof baseSchema> & {
children: Output[];
};
const schema: z.ZodType<Output, z.ZodTypeDef, Input> = baseSchema.extend({
children: z.lazy(() => schema.array())
});
Thanks to marcus13371337 and JoelBeeldi for this example.
JSON type
If you want to validate any JSON value, you can use the snippet below.
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
type Literal = z.infer<typeof literalSchema>;
type Json = Literal | { [key: string]: Json } | Json[];
const jsonSchema: z.ZodType<Json> = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]));
jsonSchema.parse(data);
Thanks to ggoodman for suggesting this.
Cyclical objects
Despite supporting recursive schemas, passing cyclical data into Zod will cause an infinite loop in some cases.
To detect cyclical objects before they cause problems, consider this approach.