Skip to content

Tool

JSON to Zod

Paste a JSON sample or a JSON Schema and get a Zod schema with matching TypeScript types. The conversion runs in your browser, and anything it cannot translate is listed instead of being dropped quietly.

An object with properties and a type or $schema key is read as JSON Schema; anything else is read as a sample. Nothing leaves your browser.

Read as: JSON sample

Zod schema

import { z } from "zod";

export const rootSchema = z.object({
  id: z.string(),
  customer: z.object({
    email: z.string(),
    vip: z.boolean(),
  }),
  items: z.array(z.object({
    sku: z.string(),
    qty: z.number().int(),
    note: z.string().optional(),
  })),
  total: z.number(),
  shippedAt: z.null(),
});

export type Root = z.infer<typeof rootSchema>;

TypeScript types

export interface Root {
  id: string;
  customer: {
    email: string;
    vip: boolean;
  };
  items: {
    sku: string;
    qty: number;
    note?: string;
  }[];
  total: number;
  shippedAt: null;
}

Two kinds of input

A JSON sample is a real payload: an API response, a webhook body, a row you logged. The converter walks it and writes the narrowest schema that accepts it. Strings become z.string(), whole numbers become integers, null stays null, and arrays merge the shapes of all their elements. If a key appears in some array elements but not others, it becomes optional, because the sample has just shown you a case where it is missing.

A JSON Schema is a description someone already wrote, usually for an API or a tool definition. If your input is an object with a properties object and a type or $schema key, it is read as a schema rather than as data. The required list decides which keys are optional, and constraints such as minimum, maxLength and enum carry over to the matching Zod calls.

A worked example

The example above is an order with a nested customer, two line items and a null shippedAt. The output is a single exported schema with an inferred type next to it. The second line item has no note, so note comes out optional on the item object. shippedAt was null in the only sample there is, so it is typed as null. That is correct for the evidence and wrong for your system, and it is the point where you should edit the result by hand to a nullable date-time string.

Turn on strict objects and every z.object becomes z.strictObject, which rejects keys the schema does not name. Use it at a trust boundary when an unknown key means the sender and receiver disagree about the contract. Leave it off when you are parsing a third-party payload that adds fields over time and you only care about the ones you read.

Why Zod for this

A TypeScript type disappears at compile time, so it cannot check data that arrives at runtime. A Zod schema is both: parse with it at the edge and you get a validated value and its type from one definition, so the two cannot drift apart. The generated module exports the schema and a type alias built with z.infer, which is the pattern worth keeping when you edit it.

For AI features this matters twice. Model output and tool arguments are untrusted input that only looks like your type. Parsing them with a schema turns a silent bad value into an error you can retry or report.

What a sample cannot tell you

Inference only knows what it was shown. One sample cannot reveal that a field is sometimes missing, that a string is really one of four values, or that a number has a ceiling. Paste several representative records inside an array to widen the result, and treat the output as a first draft to tighten, not as the contract itself.

The JSON Schema reader supports a deliberate subset: type, properties, required, items, enum, const, anyOf and oneOf as unions, allOf for plain object parts, local $ref pointers, descriptions, numeric and length bounds, additionalProperties set to false, and the common string formats email, uuid, uri, date and date-time. Keywords outside that set, such as patternProperties or if and then, are listed under Not converted so you know exactly what to add by hand.

Frequently asked questions

Is my JSON uploaded anywhere?

No. Parsing and conversion run in your browser. The last input is remembered in this browser's storage so it is there when you come back, and a share link carries the input in the URL only when you copy it.

What is not supported?

Zod source as input, since reading it would mean running your code. Remote $ref URLs, conditional keywords, pattern-based properties and a few rarer JSON Schema features are reported as not converted rather than guessed.

Why does a field come out as null or unknown?

Because the sample only ever showed null, or an empty array with nothing inside. Add a sample where the field has a real value, or edit that line by hand.

Which Zod version does the output target?

The current major API, with top-level helpers such as z.email() and z.strictObject(). If your project is on an older major, the object, array and union parts still read the same; adjust the string format helpers.

Related tools

  • Token Counter

    Count tokens for any text, exactly for OpenAI models and estimated for Claude, in your browser.

  • Claude Token Counter

    Estimate Claude tokens and input cost for a prompt, with the limits of estimating spelled out.

  • OpenAI Token Counter

    Exact o200k_base token counts for GPT-style prompts, computed locally.

  • Agent Framework Picker

    Five questions to decide between a plain loop, an SDK, or a framework.

  • LLM Cost Calculator

    Estimate what an LLM feature costs per request, per day and per month from your own token counts and prices.

  • Agent Run Budget Simulator

    See how an AI agent's cost grows turn by turn and where your budget cap would stop it.

  • Tool Schema Validator

    Check an OpenAI, Anthropic or MCP tool definition for common mistakes and convert it to the other two formats.

Read the guide →