JSON to Zod: generate a schema from a sample
Zod is a TypeScript-first validation library: you describe the shape of data once, and get both runtime validation and a static type through z.infer. Writing schemas for large API payloads by hand is tedious, so this JSON to Zod generator does the first draft for you. It infers types from a JSON sample with quicktype and prints one z.object schema per object shape, ordered so that every schema is defined before it is used.
Example
Input (class name Task):
{
"id": 42,
"title": "Write docs",
"done": false,
"priority": 2.5,
"tags": ["docs"],
"owner": { "name": "Ada", "email": null },
"due": "2026-10-01T09:00:00Z",
"subtasks": [
{ "title": "Outline", "estimate": 2 },
{ "title": "Draft" }
]
}
import * as z from "zod";
export const OwnerSchema = z.object({
"name": z.string(),
"email": z.null(),
});
export type Owner = z.infer<typeof OwnerSchema>;
export const SubtaskSchema = z.object({
"title": z.string(),
"estimate": z.number().optional(),
});
export type Subtask = z.infer<typeof SubtaskSchema>;
export const TaskSchema = z.object({
"id": z.number(),
"title": z.string(),
"done": z.boolean(),
"priority": z.number(),
"tags": z.array(z.string()),
"owner": OwnerSchema,
"due": z.coerce.date(),
"subtasks": z.array(SubtaskSchema),
});
export type Task = z.infer<typeof TaskSchema>;
Validate incoming data with TaskSchema.parse(data), which throws a ZodError describing every mismatch, or safeParse to get a result object instead.
How JSON maps to Zod
- Strings, numbers and booleans become
z.string(),z.number()andz.boolean(). Integers and decimals are bothz.number(); add.int()yourself where it matters. - ISO date strings become
z.coerce.date(), which converts the string into aDateduring parsing. - Keys missing from some objects in an array get
.optional(). - Arrays become
z.array(...); arrays with mixed element types use a union. - A value that is only ever
nullin the sample becomesz.null(); values that are sometimes null become a union such asz.union([z.null(), z.string()]), which you can shorten toz.string().nullable().
Refining the generated schema
A sample can only tell the generator what types values have, not what rules they follow. After generating, add constraints that document your API contract:
export const TaskSchema = z.object({
id: z.number().int().positive(),
title: z.string().min(1).max(200),
done: z.boolean(),
priority: z.number().min(0).max(5),
tags: z.array(z.string()),
owner: OwnerSchema,
due: z.coerce.date(),
subtasks: z.array(SubtaskSchema),
});
Other common edits: replace z.string() with z.enum(["open", "closed"]) for fields with a fixed set of values, use z.string().email() or .url() for formatted strings, and change a z.null() into z.string().nullable() when you know the field can hold text. By default z.object strips unknown keys; use .strict() if extra keys should be rejected.
Using the schema with fetch
const res = await fetch("/api/tasks/42");
const result = TaskSchema.safeParse(await res.json());
if (!result.success) {
console.error(result.error.issues);
} else {
const task: Task = result.data; // fully typed, dates already converted
}
Validating at the boundary like this means the rest of your code can trust the data's shape, and a changed API shows up as a clear validation error rather than an undefined property deep in your UI.
Zod versions
The output uses the import * as z from "zod" form and basic schema methods that work in Zod 3 and Zod 4. If your project pins an older major version, check the import style it expects.
If you only need static types, JSON to TypeScript produces plain interfaces. For language-neutral validation, JSON to JSON Schema generates a draft-06 JSON Schema. Format the result with the TypeScript Formatter. All generation happens in your browser.