json

FormatValidateConvert

JSON to Zod

JSON
Language
Structured output

Result· read-only

—Zod

The result appears here.

Generate

JSON to Zod: generate a Zod schema from JSON.

Your document never leaves the browser.

Zod schemas and their inferred types, from a JSON sample — in your browser.

TypeScript types vanish when the code runs, so they cannot stop a malformed response. A Zod schema can, and this page writes one from a sample, along with the types Zod infers from it.

.int()

so qty rejects 2.5 while price accepts it

z.infer

one exported type per schema, so they cannot drift

0

bytes leave your machine

Schemas in the order they are needed

Each object becomes an exported z.object constant, named after its type with Schema on the end. A schema must exist before another one uses it, so the nested ones come first: CustomerSchema and ItemSchema are written before the root. Whole numbers become z.number().int(), so qty rejects 2.5 while price accepts it.

note appears in every line item and is null in one of them, so it is z.string().nullable(). gift-wrap is absent from one, so it is z.boolean().optional(). The key keeps its quotes and its hyphen, because a Zod object’s keys are the document’s own and nothing needs renaming.

1

The sample, an order with two line items, pasted as the source

{
  "order_id": 1042,
  "placed_at": "2026-09-25T10:15:00Z",
  "paid": true,
  "customer": { "name": "Ada Lovelace", "email": "[email protected]" },
  "items": [
    { "sku": "PEN-01", "qty": 2, "price": 3.5, "note": null },
    { "sku": "INK-07", "qty": 1, "price": 12, "note": "Fragile", "gift-wrap": true }
  ]
}
2

The schemas written for it

import { z } from "zod";

export const CustomerSchema = z.object({
  name: z.string(),
  email: z.string(),
});

export const ItemSchema = z.object({
  sku: z.string(),
  qty: z.number().int(),
  price: z.number(),
  note: z.string().nullable(),
  "gift-wrap": z.boolean().optional(),
});

export const RootSchema = z.object({
  order_id: z.number().int(),
  placed_at: z.string(),
  paid: z.boolean(),
  customer: CustomerSchema,
  items: z.array(ItemSchema),
});

export type Customer = z.infer<typeof CustomerSchema>;
export type Item = z.infer<typeof ItemSchema>;
export type Root = z.infer<typeof RootSchema>;

Options

The sample, with import line off and z.infer type exports off

export const CustomerSchema = z.object({
  name: z.string(),
  email: z.string(),
});

export const ItemSchema = z.object({
  sku: z.string(),
  qty: z.number().int(),
  price: z.number(),
  note: z.string().nullable(),
  "gift-wrap": z.boolean().optional(),
});

export const RootSchema = z.object({
  order_id: z.number().int(),
  placed_at: z.string(),
  paid: z.boolean(),
  customer: CustomerSchema,
  items: z.array(ItemSchema),
});

Option

import line

import line: on by default, writing import { z } from "zod" at the top. Turn it off when the file already imports it.

Option

z.infer type exports

z.infer type exports: on by default, one export type per schema, so the types and the validators cannot drift apart. Turn it off if you keep your types elsewhere.

Validating what arrives

Call RootSchema.parse on the parsed response and you get either typed data or a ZodError listing every path that failed. safeParse does the same without throwing, returning success and either the data or the error.

Two objects that share the same keys and types, anywhere in the document, share one schema constant too. The schemas are strict about types and loose about extra keys, which is Zod’s own default: a key the sample never had is stripped from the result, not rejected. Add .strict() to an object where an unknown key should be an error, and refine strings with .email() or .datetime() where your data promises one.

1

Two addresses of one shape

{
  "from": { "city": "Paris" },
  "to": { "city": "Lyon" }
}
2

One schema constant for both

import { z } from "zod";

export const FromSchema = z.object({
  city: z.string(),
});

export const RootSchema = z.object({
  from: FromSchema,
  to: FromSchema,
});

export type From = z.infer<typeof FromSchema>;
export type Root = z.infer<typeof RootSchema>;

Your sample stays here

Inference and code generation both run inside this browser tab. The sample is never uploaded, stored on a server or logged, because the page has no server to send it to, and once loaded it carries on working with the connection off.

Read next:

FAQ

Frequently asked questions

Didn’t find your answer?Write to us on the contact page →
Which Zod version does it target?

The output uses the core API that Zod 3 and Zod 4 share: z.object, z.array, z.union, nullable and optional.

Is this the same as the TypeScript page?

The shapes are the same; the difference is that Zod checks them at run time. The exported types match what the TypeScript page would write.

How do I handle a date string?

It is z.string() here, since JSON has no date type. Change it to z.string().datetime() to check the format, or add .transform to turn it into a Date.

Keyboard shortcuts

Send feedback

Questions, bug reports and feature requests are all welcome. A bug report is easiest to act on with the shape of the document that caused it — never send anything confidential.

Email us

Contact page, in a new tab, so this page stays as it is.

Settings

Indent

The result is written with it — YAML and XML at 2 spaces when it is Tab.

Code text size
14 px

Both panes.

Wrap long lines

Load from a URL

Your browser fetches it directly — the request goes to that site, never to us.