A Zod schema generated from a single JSON sample does not prove an API contract — it documents only what one value happened to show. Proving a contract means demonstrating that every valid response satisfies the documented rules and every invalid response is rejected, which requires fixtures broader than a single payload. The JSON to Zod Schema Converter reads one valid JSON value, walks its structure, and emits dependency-free Zod source code so a reviewer can compare the inferred shape against authoritative documentation. Required-field inference comes from "present in every sampled object," nullable inference comes from "observed null alongside another type," and array element inference comes from "every element seen inside the stated budget." None of those rules is the same as "the API requires this." A field present in your example may still be optional in production, a value absent from the sample may be perfectly valid, and any string format, numeric range, enum, literal, discriminated union, or business rule that the sample did not encode remains unknown. Treat the output as a starting review draft, then tighten it against documentation, additional fixtures, and a Zod version pinned in your own project before it stands in for the real contract.

does the generated schema prove my api contract when i convert json to zod schema
Does a Generated Zod Schema Prove Your API Contract?

What "Proving the Contract" Would Actually Require

A proven contract has three properties that a single inference step cannot deliver. It defines every required field for every variant, it rejects every shape that violates a rule, and it stays stable across the payloads the server actually emits. To establish that, you need at least:

  • An authoritative specification — OpenAPI, JSON Schema, Protobuf, or hand-written contract notes — describing the documented rules.
  • A representative fixture set covering happy paths, optional-field absences, nullable values, and known rejection cases.
  • A validator compiled against a pinned schema library version and exercised against the fixtures, both green and red.

Sample inference gives you the first sketch. The rest is verification work that has to happen against real documentation, not against the JSON you happened to paste in. The generator is explicit about this: its output describes what was observed, and required status, formats, ranges, enums, and business rules must be checked against authoritative documentation and broader fixtures before the schema can validate a real contract.

What the Converter Observes From One JSON Sample

The generator maps each encountered JSON value to a documented Zod constructor and emits the result as readable source. Finite numbers become z.number(), strings become z.string(), booleans become z.boolean(), and JSON null becomes z.null(). Objects become z.object({...}) calls whose property order follows the order in which keys were first encountered. Keys that are already valid JavaScript identifiers stay unquoted; spaces, hyphens, numeric starts, quotes, and other unsafe names are emitted with JSON string escaping so the generated shape remains valid source.

For arrays, the tool walks every element inside the input budget. A homogeneous sample produces a single element schema. A mixed sample — for example, an array containing a string, a number, and an object — produces a deterministic z.union([...]). If one of the observed element types is null, the union's other members are wrapped in .nullable() rather than emitting a separate z.null() arm. Nested arrays and objects are handled recursively, and when an array contains objects, the keys across those objects are merged.

That merging rule is exactly where the contract-vs-sample gap appears. A property observed in every sampled object stays required. A property missing from at least one object becomes optional, because the sample did not prove it is always present. Values observed under the same key are recursively merged, so a field that sometimes holds a string and sometimes holds an object becomes a union under that property. Repeated conversion of the same JSON produces stable, deterministic source because the unions are deduplicated and ordered consistently.

How to Generate a Reviewable Zod Schema From a Sample

  1. Pick one valid and representative JSON value — a typical happy-path response, or the smallest fixture that exercises the variants you care about — and paste it into the converter along with a valid JavaScript identifier for the exported schema name.
  2. Run the generator and read the emitted source carefully. Inspect every .optional(), .nullable(), union arm, numeric choice, and any z.array(z.unknown()) that came from an empty array, because each one is a place where the sample did not give enough evidence for the generator to be more specific.
  3. Copy the emitted source into a project where Zod is already installed and pinned to the version you intend to ship, then compile the file. The converter does not install or run Zod for you; that step happens in your own codebase.
  4. Test the resulting schema against the authoritative data contract: parse the API documentation, run it against representative fixtures that include both accepted and rejected production cases, and write down every rule the schema did not capture.
  5. Add the missing rules by hand — formats, ranges, enums, integer constraints, discriminated unions, defaults, refinements — using only documentation or production fixtures as your source. Do not invent rules to fill gaps that the sample did not actually demonstrate.

Limits the Generator Deliberately Refuses to Guess

Sample inference is honest about what a small JSON payload cannot establish. The tool will not infer any of the following, because spellings and one-shot samples cannot prove intent:

  • String formats — UUIDs, email addresses, URLs, dates, ISO timestamps.
  • Numeric constraints — minimums, maximums, integer-only flags, exclusive ranges.
  • Enums, literal unions, and discriminated unions with a tagged key.
  • Default values, transformations, coercions, refinements, and brand types.
  • Records, tuples, strict-object passthrough rules, and recursive references back to the schema itself.
  • Business rules expressed as cross-field predicates or contextual invariants.

Numbers have already passed through JSON.parse and therefore follow JavaScript number precision; identifiers or currency values that require exact decimal semantics should usually remain strings rather than be converted to z.number(). JavaScript comments, trailing commas, NaN, Infinity, undefined, BigInt syntax, single-quoted strings, and bare object literals are rejected by the parser rather than silently repaired. The generator also returns an explicit error if a request exceeds its resource bounds: 500,000 input characters, 50,000 visited values, 40 nesting levels, or 1,000,000 generated characters. A limit returns an error and never emits a partial schema.

What the converter infersWhat you must add yourself
Primitive types (string, finite number, boolean, null)String formats (UUID, URL, email, date)
Object shapes and source-order keysEnums and literal unions
Nested arrays and recursively merged objectsNumeric ranges and integer constraints
Optional fields when missing from at least one sampleDiscriminated unions with a tagged key
Nullable fields when null was observed with another typeDefaults, transformations, refinements, brands
Homogeneous and mixed element schemas for non-empty arraysTuples, records, recursive references, strict objects
z.array(z.unknown()) for empty arraysBusiness rules and cross-field predicates

Turning an Inferred Schema Into a Real Contract Test

The output of the converter is a starting draft, not a deliverable. To convert it into something that proves your contract, you need a workflow that uses the inferred shape as scaffolding and the authoritative documentation as the truth source. A practical sequence looks like this:

  • Open the official API spec or contract notes and list every required field, allowed value, format, and range. Mark each item as "captured" or "missing" against the inferred schema.
  • Add the missing rules as .refine(), .regex(), z.enum(), z.literal(), or numeric .min()/.max()/.int() calls. The Zod API documentation lists the available constructors; pick the version that matches the one you pinned.
  • Build a fixture suite that includes at least one accepted payload per documented variant and at least one rejected payload per documented rule. Run the schema through safeParse on each fixture and assert the result.
  • Wire that fixture run into your CI pipeline so a contract change has to update both the schema and the tests in the same commit.

Following that loop converts a sample-derived sketch into a maintained contract artifact. Without the documentation pass and the fixture tests, the inferred schema only proves what one JSON value contained — which is useful for review but never enough on its own.

Frequently Seen Mistakes When Relying on Inference

Two pitfalls come up often enough to call out. The first is treating a single captured response as the full contract; production APIs add fields, drop fields under certain conditions, and accept multiple shapes per endpoint. The second is using the inferred schema as a generator of test data rather than as a check against real data — both directions are useful, but the schema's job is to reject invalid shapes, not to invent them. Reading the inferred source as a description of one observation, not as a definition of the API, is the habit that keeps the workflow honest.

For a deeper look, see PX to REM Without an API: A Local Browser Workflow.

For a deeper look, see Decode ULID Timestamps in Bulk Without an API.