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.

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.
- 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.
- 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.
- 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 observation | Inferred Zod output | Must be added manually |
|---|---|---|
| String value | z.string() | Format (email, URL, UUID), min and max length, regex, literals |
| Finite JSON number | z.number() | Integer check (.int()), min, max, finite, multipleOf |
| Boolean value | z.boolean() | Literal constraints, custom refinements |
| null alongside another type | <type>.nullable() | Whether the field can also be omitted (.optional()) |
| Empty array | z.array(z.unknown()) | Real element type, min and max length |
| Mixed primitive array | z.array(z.union([...])) | Discriminator, literal tags, brand |
| Object with consistent keys | z.object({...}) | .strict(), .passthrough(), .catchall(), defaults, transforms |
| Number requiring exact decimal semantics | z.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.
| Bound | Value | Effect when exceeded |
|---|---|---|
| Input characters | 500,000 | Explicit error, no output |
| Visited values during inference | 50,000 | Explicit error, no output |
| Nesting depth | 40 | Explicit error, no output |
| Generated source characters | 1,000,000 | Explicit error, no output |
| JavaScript number precision | Inherited from JSON.parse | High-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.