The JSON to Zod Schema Converter is a browser-side tool that turns one valid JSON value into readable, deduplicated Zod source code plus an inferred TypeScript type, using strict JSON parsing and documented inference rules with explicit limits on what is guessed. The page does not load Zod, does not execute the generated code, and does not upload your sample; it parses strict JSON, recursively walks the structure, and emits a stable text representation that you copy into your own project.

The transformation follows a small set of documented rules. Primitive JSON values map to the corresponding Zod constructors: strings to z.string(), finite JSON numbers to z.number(), Booleans to z.boolean(), and null to z.null(). Object keys appear in source encounter order, valid JavaScript identifiers stay unquoted, and unsafe names (spaces, hyphens, numeric starts, quotes) are emitted with JSON string escaping so the generated shape remains valid source. Arrays are inferred from every element in the stated input budget; homogeneous samples produce one element schema, mixed samples produce a deterministic z.union, and an empty array deliberately becomes z.array(z.unknown()) because no evidence of element type is available. Nested arrays and objects are handled recursively, and when an array contains objects, their keys are merged into a single shape.

generate zod schema from json
Generate a Zod Schema From JSON: Rules and Limits

How the converter maps JSON to Zod source

The inference engine runs in three predictable phases. First, the input string is parsed with strict JSON rules; JavaScript comments, trailing commas, NaN, Infinity, undefined, BigInt syntax, single-quoted strings, and object literals are rejected rather than silently repaired. Second, the parsed tree is walked recursively and each node is mapped to a Zod expression using the documented constructors listed above. Third, identical schemas are deduplicated and mixed-value unions are sorted deterministically, so converting the same JSON twice produces byte-stable source.

The merge rules are the part that surprises most readers. When an array contains multiple object samples, the converter unions their keys: a property observed in every sampled object stays required, while a property missing from at least one object becomes .optional(). Values observed under the same key are recursively merged, so a property that is sometimes null and sometimes a string becomes z.string().nullable(), and a property that is sometimes a string and sometimes an object becomes z.union([z.string(), z.object({...})]). These rules apply at every nesting level, not only at the root.

Generate a Zod schema from one JSON sample

The workflow below reflects the three documented operating steps and the verification loop you should add before merging the schema into a real codebase.

  1. Paste one valid and representative JSON value (object, array, or primitive) into the converter, then enter a valid JavaScript identifier as the schema name. The identifier must not be a reserved word and must follow standard JavaScript naming rules.
  2. Generate the Zod source, then inspect every choice the tool made: optional fields, nullable unions, mixed array unions, empty arrays, numeric precision, and quoted keys for unsafe property names. Each of these is an inference, not a contract proof.
  3. Copy the emitted source into a project where Zod is installed, format it, compile it, and test it against the authoritative data contract using fixtures that include both accepted and rejected cases. The page does not install Zod or run validation; that step happens in your project.

What the converter deliberately does not infer

The most common mistake when adopting an inferred schema is treating it as a complete contract. The generator refuses to invent rules that a single sample cannot establish, and the related reasoning is explored in the dedicated article on whether generated Zod schemas prove an API contract.

JSON observationInferred Zod outputMust be added manually
String valuez.string()Format (email, URL, UUID), min and max length, regex, literals
Finite JSON numberz.number()Integer check (.int()), min, max, finite, multipleOf
Boolean valuez.boolean()Literal constraints, custom refinements
null alongside another type<type>.nullable()Whether the field can also be omitted (.optional())
Empty arrayz.array(z.unknown())Real element type, min and max length
Mixed primitive arrayz.array(z.union([...]))Discriminator, literal tags, brand
Object with consistent keysz.object({...}).strict(), .passthrough(), .catchall(), defaults, transforms
Number requiring exact decimal semanticsz.number()Keep as a string for currency, identifiers, and high-precision decimals

The general rule is simple: spellings and small samples cannot establish intent. If your contract guarantees an integer or an enum literal, write that constraint yourself from authoritative documentation and broader fixtures.

Required, optional, and nullable fields from merged samples

Required versus optional is decided per key by comparing every object inside a sample array (and every nested object encountered recursively). A key seen in 100% of sampled objects stays required; a key missing from any sample becomes .optional(). This is the most likely place for a real contract to disagree with the inferred schema: a field present in one example can still be optional in production, and a value absent from the sample may be perfectly valid.

Nullability is decided symmetrically. If null is ever observed under a key together with another type, the inferred schema becomes <type>.nullable(). If null is the only value observed under a key, the schema becomes z.null(). Unsafe property names are quoted with JSON string escaping, so a key like "first name" survives the round-trip into valid JavaScript source. The same escaping applies to keys that start with a digit, contain hyphens, or contain characters that would otherwise break the object literal.

Input limits and parsing rules to know

The converter is bounded by four hard limits, plus the precision inherited from the JSON parser. Exceeding a bound returns an explicit error and never emits a partial schema. Deep or very wide examples can still be poor contract documentation even when these limits are not hit, so prefer a small representative fixture and split independent payloads into separate schemas.

BoundValueEffect when exceeded
Input characters500,000Explicit error, no output
Visited values during inference50,000Explicit error, no output
Nesting depth40Explicit error, no output
Generated source characters1,000,000Explicit error, no output
JavaScript number precisionInherited from JSON.parseHigh-precision decimals lose precision; keep as strings

After copying: install, format, compile, test

The page emits source text only. Three things happen next, in this order. First, install and pin the intended Zod version in your destination project; the official constructor reference at zod.dev/api lists every expression the generated source can include, and the zod source repository pins the current release. Second, format the source with your project's formatter and confirm it compiles under TypeScript. Third, run a focused test suite that covers both the accepted cases your fixture demonstrated and the rejected cases your authoritative documentation forbids (extra fields, missing required fields, wrong types, out-of-range values). Only after that test pass is the inferred schema safe to wire into request validators, form schemas, or API response guards.

Because the same JSON produces stable source on repeat runs, the converter fits well into documentation-driven workflows: paste a real payload, capture the generated schema as a starting file, then tighten it with the rules the contract actually requires.