JSON to Zod Schema: Generate a Validator From JSON
Turn JSON into a Zod schema free. Get z.object types with optional, nullable and array handling, plus the inferred TypeScript type and the npm command.
To turn JSON into a Zod schema, paste a representative sample into the JSON to Zod schema builder, name the schema, and choose how unknown keys should behave. The tool produces a z.object({...}) expression, an inferred TypeScript type, and the npm i zod command. It is a starting point for runtime validation, not just a type declaration.
The distinction matters. A TypeScript interface disappears at compile time; a Zod schema still exists at runtime and can reject a malformed API response. Generating the schema from a real payload gets you both the shape and the validator without transcribing every field by hand.
What the output looks like
For a sample like {"name":"Ada","age":36,"email":null,"tags":["ts"],"address":{"city":"London"}}, the generator emits:
import { z } from 'zod';
export const mySchema = z.object({
name: z.string(),
age: z.number().int(),
email: z.string().nullable(),
tags: z.array(z.string()),
address: z.object({
city: z.string(),
}),
});
export type MySchema = z.infer<typeof mySchema>;
The schema is a value you can call .parse() on. The exported type is derived from it, so the two cannot drift apart. Nested objects are inlined rather than split into separate named schemas, which keeps a small payload readable.
How to generate a Zod schema
- Open the JSON to Zod schema builder.
- Paste a JSON sample. Real responses produce better schemas than minimal mocks.
- Set the schema name. The default is
mySchema; the exported type is derived in PascalCase. - Choose unknown-key behaviour: strip (default), reject with
.strict(), or keep with.passthrough(). - Press Generate.
- Copy the code into your project and run
npm i zodif it is not already installed.
If the root is an array or a scalar rather than an object, the schema is the matching z.array(...) or scalar validator instead of an object.
Choosing an unknown-key mode
| Mode | Generated call | Behaviour |
|---|---|---|
| Strip (default) | z.object({...}) |
Unknown keys are removed from the parsed result |
| Reject | .strict() |
Unknown keys cause a validation error |
| Keep | .passthrough() |
Unknown keys are preserved |
Strip is the usual default because it tolerates APIs that add fields over time while still giving you a clean, typed object. Strict is useful for configuration and internal contracts where an unexpected key signals a bug. Passthrough is for forwarding payloads where extra fields must survive.
In the current generator, the chosen mode is applied to nested objects as well as the root, not only the outermost object. That is worth knowing if you expect a strict root with permissive children; adjust the nested calls manually if so.
Type mapping
| JSON value | Zod schema |
|---|---|
| String | z.string() |
| Integer | z.number().int() |
| Decimal | z.number() |
| Boolean | z.boolean() |
| null | z.any() (or .nullable() on a known type) |
| Object | Inline z.object({...}) |
| Array | z.array(T) |
| Mixed or empty array | z.array(z.any()) |
Integers and decimals are distinguished, which is more precise than treating every number as number. A JSON integer becomes .int(), so 3.5 fails validation where it should.
Optional and nullable fields
The generator merges samples of the same object shape, including elements of an array. Two flags come out of that:
- Optional (
.optional()) when a key is absent from some samples. - Nullable (
.nullable()) when a key is present but null in some samples.
When both apply, the order is .nullable() then .optional(), matching Zod's chain semantics. This is the part most hand-written schemas get wrong: an API that sometimes omits a field and sometimes sends null needs both, not one or the other.
A field that is null in every sample becomes z.any(), because there is no type to infer. Replace it with the real type once you know what the API can return.
Common problems and fixes
The schema accepts everything. A field that was null in every sample became z.any(), or an array was empty and became z.array(z.any()). Provide a populated sample and regenerate.
A required field is marked optional. It was missing from at least one merged sample. Either the API is genuinely inconsistent, or your sample combined different shapes. Check the source data.
Validation fails on extra keys. The schema was generated with strict mode. Switch to strip or passthrough, or add the missing keys to the sample.
Dates are just strings. Zod validates shapes, not semantics. If a string must be a date, replace z.string() with z.string().datetime() or a transform into a Date.
The schema name is invalid. It must be a legal JavaScript identifier. The generator adjusts a leading digit and appends Schema when the name collides with a reserved word.
The JSON will not parse. The input must be strict JSON. Remove comments, trailing commas and single quotes; the JSON formatter will point at the syntax error.
A large payload produces a huge inline schema. Nested objects are inlined by design. Split shared shapes into named schemas yourself once the structure stabilises.
Tips for schemas you can keep
- Start from production-shaped data. Include optional fields, nulls and a few array elements so the merge logic has something to work with.
- Use
.parse()at the boundary. Validate the response where it enters the app, not deep inside a component. - Prefer strip for third-party APIs. Strict mode against an API you do not control creates failures whenever the provider adds a field.
- Add refinements after generation. Length limits, email formats and date formats are domain rules the generator cannot infer.
- Keep the inferred type.
z.infer<typeof schema>avoids maintaining a parallel interface. - Generate the same sample for other ecosystems. The JSON to Rust struct generator and JSON to Go struct generator follow the same sample-based approach.
- Watch Zod versions. The generated code uses the standard
z.object,.nullable(),.optional(),.strict()and.passthrough()API, which works across current Zod releases. Check your installed version if a method is missing.
Zod schema vs TypeScript type
A TypeScript type is erased at build time. It cannot tell you that a response from the network is wrong; it only tells the compiler what you promised. A Zod schema runs, so it can reject age: "36" before the value reaches your code.
That is why generating a schema is more useful than generating an interface. The inferred type gives you editor autocomplete, and the schema gives you a runtime check. You get both from one sample.
The trade-off is bundle size and a small runtime cost. For a browser app, Zod adds a dependency; for a server boundary or a test suite, it is usually easy to justify.
Frequently asked questions
Is the JSON to Zod generator free?
Yes. There is no account and no charge.
Does my JSON get uploaded?
No. The conversion runs in your browser and the JSON is not stored.
Does it produce a runtime validator or just a type?
Both. It generates a Zod schema and a TypeScript type inferred from that schema with z.infer.
How are optional and null fields handled?
Missing keys become .optional(), null values become .nullable(), and fields that are both get both. The generator merges array elements of the same shape before deciding.
Which unknown-key mode should I use?
Strip (the default) for third-party APIs, strict for internal contracts and configuration, and passthrough when extra fields must be preserved.
Which Zod version is required?
The output uses the stable core API available in current Zod releases. Install it with npm i zod and check the version if a chained method is unavailable.
Bottom line
A generated Zod schema gives you a runtime guard and a TypeScript type from the same source of truth. Start with a realistic sample, choose strip unless you control both sides, and add domain refinements after generation. Open the JSON to Zod schema builder, and if the same data also needs a compiled-language model, generate the matching Go structs from the same sample.
Related posts
View allIBAN Validator: Check an IBAN Before You Send Money
One mistyped character can bounce a payment. Check the country, length and mod-97 checksum of an IBAN before you send it.
Base64 Encode and Decode: A Practical Guide
Encode text to Base64 or decode it free in your browser. UTF-8 safe, line breaks tolerated, size and character counts shown, with errors on invalid input.
Convert CSV to JSON: Keep Types and Headers Right
Convert CSV to JSON free with auto delimiter detection, typed numbers and booleans, duplicate-header handling and 2/4-space or tab output. Runs in browser.
Comments (0)
Be the first to share feedback on this post.