JSON to Zod Schema

JSON to Zod Schema - Generate a Zod Schema and TypeScript Type

Turn a JSON sample into a Zod 4 schema module for TypeScript. Paste JSON or choose a .json file, then copy or download the generated code. The JSON is read in your browser and never uploaded.

UTF-8 files only, up to 5 MB. The file is read locally and never uploaded.

The root name is used for the top schema and its type. Object schemas for nested keys are named from those keys; invalid or reserved names are repaired.

Ready. Nothing leaves your browser.

The schema describes the sample you pasted, so review it before relying on it. Arrays are sampled up to 1000 elements, input is limited to 5 MB and 200 levels of nesting.

How to convert JSON to a Zod schema

  1. Add a JSON sample

    Paste a response or select a .json file. The file is read locally, converted from UTF-8 and never sent anywhere.

  2. Set the options

    Choose the root name and whether whole numbers get .int(), objects get .strict(), nested objects stay inline, missing keys become optional or nullish, and types are exported.

  3. Copy or download

    Click Generate Zod schema, then copy the module or save it as schemas.ts. It imports z from the zod package, which you add to your own project.

Paste JSON, click Generate Zod schema, and the tool writes one export const per object plus a z.infer type for each. Nested schemas are declared before the schemas that reference them, so the module compiles as written.

When to use this JSON to Zod converter

You receive a third-party payload and want a runtime check at the boundary. Paste one real response, get a schema, then tighten the rules by hand where the sample is incomplete.

You are writing tests or fixtures and want the same shape described twice: once as a Zod schema for validation and once as an inferred TypeScript type.

You are prototyping an API and the JSON contract is still moving. A generated schema is faster to edit than a hand-written one, and the compiler checks the inferred types.

You need to see what a JSON sample actually contains, including optional keys that only appear in some array elements.

How JSON values map to Zod schemas

Mapping used by this converter. Every schema is inferred from the sample you provide.
JSON value Zod schema Notes
object z.object({ ... }) Nested objects become their own named schema unless inlining is on.
string z.string() Email, URL and ISO date-time checks are opt-in only.
number z.number() Whole numbers get .int() when that option is enabled.
boolean z.boolean() true or false.
null z.null() A value seen as both null and something else becomes .nullable().
array of one kind z.array(z.string()) All sampled elements share one schema.
empty array z.array(z.unknown()) An empty array carries no element information.
array of objects z.array(ItemSchema) Keys from all sampled elements are merged; a missing key becomes optional.
mixed array z.union([ ... ]) One member per observed element type.
root array z.array(ItemSchema) The root schema wraps the item schema; objects are named from the root name.

Options explained

Root schema name. Names the top level const and the exported type: Root produces RootSchema and Root. Invalid characters and reserved words are repaired instead of producing code that cannot compile.

.int() for whole numbers. Off by default. When on, every number that was a whole number in the sample gets .int(); decimals stay z.number().

.strict() on objects. Off by default. Strict objects reject keys that are missing from the schema, which is useful for catching drift and painful for evolving APIs.

Inline nested objects. Off by default. Nested objects are written as separate named schemas declared before use; with inlining they appear inside the parent schema.

Optional style. A key missing from some array elements gets .optional() by default. Switch to .nullish() when the API also sends explicit nulls for the same key.

Export inferred types. On by default. Adds export type Name = z.infer<typeof NameSchema> for every schema, so the module provides both the validator and the type.

Detect string formats. Off by default. When on, every string sample must look like an email, an http(s) URL or an ISO date-time ending in Z before z.email(), z.url() or z.iso.datetime() is written. A single counterexample keeps the field a plain string.

Keys. Keys that are not valid identifiers are quoted, keys with dots or spaces keep their exact text, and __proto__ is written as a computed key so it cannot change an object prototype.

Limitations

The output describes one sample and is a starting point for validation, not a finished contract. These limits are deliberate:

One sample. Fields absent from the sample cannot be inferred, and each type comes only from the values that appear in it. Values that only appear in production responses are missing from the schema.

Merged objects. Array elements are merged into one schema, so a key missing from some elements becomes optional and a key seen with several types becomes a union. APIs that return genuinely different shapes per element need hand-written z.discriminatedUnion.

Empty containers. An empty array becomes z.array(z.unknown()) and an empty object becomes z.object({}). Replace them with real fields once you know them.

No recursion. A tree that repeats the same shape produces separate named schemas, not z.lazy(). Add the recursive schema by hand.

Formats are guesses. Email, URL and ISO date-time detection is a pattern match on the samples you provide. It is off by default because a sample is not a contract.

Numbers. JSON numbers become JavaScript doubles, so there is no distinction between integer and float beyond Number.isInteger. A value such as 1e999 parses to Infinity, which z.number() rejects at runtime.

Size limits. Input is limited to 5 MB and 200 levels of nesting. Arrays are sampled up to 1000 elements and the module notes when that happened, so ignored elements can carry keys that are missing from the schema.

Zod 4 only. The generated module uses Zod 4 APIs such as z.email() and z.iso.datetime(). Zod 3 projects need different calls; the tool does not emit them and the browser does not run the schema.

Validation itself happens in your project: the generated module is checked by the converter's test suite, but this page never executes the schema against your data.

JSON to Zod FAQ

Is my JSON uploaded anywhere?

No. The text you paste and any .json file you choose are processed in this browser tab. Loading the page still requests ordinary website files such as HTML, styles and scripts, and servers or proxies may record those requests.

Which Zod version does the generated code target?

Zod 4. The module starts with import { z } from "zod" and uses current APIs only, for example z.email(), z.url() and z.iso.datetime() instead of the deprecated string methods from Zod 3.

Does the schema validate my data on this page?

No. The page only generates text. The generated module is compiled and executed by this tool's test suite, but you run it in your own project with Schema.parse(value) or Schema.safeParse(value).

Why is an empty array typed as z.array(z.unknown())?

JSON does not record what an empty array would contain. Without an element there is nothing to infer, so the converter writes z.array(z.unknown()) and leaves the decision to you.

Why do some keys become optional or nullable?

A key missing from some elements of an array becomes optional, because the sample shows it is not always present. A key that holds both null and a value becomes nullable. Both choices follow what the sample showed, not what the API promises.

Can I use the output with a custom root name or without types?

Yes. Set the root name before generating, and clear the export types checkbox to drop the z.infer aliases. Invalid or reserved root names are converted to a legal identifier instead of producing broken code.

Related tools