What this tool does
A JSON sample becomes Zod schemas, one const per object.
{ "id": 1, "name": "Ada", "address": { "city": "Paris" } }
import { z } from "zod";
export const AddressSchema = z.object({
city: z.string(),
});
export const RootSchema = z.object({
id: z.int(),
name: z.string(),
address: AddressSchema,
});
Children come first: these are runtime constants, and a schema has to exist before another one mentions it.
Numbers are read as they were written
z.int() for an integer, z.number() for anything written with a decimal point or an exponent:
{ "price": 10.0, "quantity": 3 }
price: z.number(),
quantity: z.int(),
Until recently price came out z.int(), because 10.0 parses to a whole number and nothing kept the literal. The schema then rejected 10.5 — the next record, most likely. JSON has one number type, so the written form is the only evidence of what the producer meant; the conversion now reads it. This was found while writing this page and fixed.
Zod 4 syntax
z.int() is the Zod 4 spelling. On Zod 3, write z.number().int(); nothing else in the output differs between the two versions.
What a sample cannot decide for you
- Nothing is
.optional(). A key seen once can appear; whether it always does is not in the document. nullgivesz.null(), a schema that accepts nothing else. If the field is “a string, sometimes absent”,.nullable()is yours to add.- Unknown keys are stripped, not refused, because that is Zod’s default.
.strict()is a decision about your API. - A mixed array becomes a union:
[1, "a"]givesz.array(z.union([z.int(), z.string()])), which describes the sample exactly and may well be wider than your real data.
Each of these is a place where the generated schema is a starting point, and the page says so rather than letting you find out in review.
Private by design
Everything runs locally in your browser with JavaScript. Your data is never uploaded, which makes the tool safe for sensitive content, and it keeps working offline.