Paste any JSON sample and this converter writes TypeScript interfaces for compile-time checks and a Zod schema for checking data at runtime. Nested objects get their own named types, arrays of objects are merged so fields absent from some items turn optional, and the conversion happens locally, so private payloads stay on your machine.
| JSON value | TypeScript | Zod |
|---|---|---|
| "text" | string | z.string() |
| 42 or 4.2 | number | z.number() |
| true | boolean | z.boolean() |
| null | null | z.null() |
| [1, 2] | number[] | z.array(z.number()) |
| {"a": 1} | interface with a: number | z.object({ a: z.number() }) |
| Key missing in some array items | key?: type | .optional() |
Updated
Types are only as good as the sample, so paste a response that shows every field you expect, ideally several array items. Rename the root type, switch between interface and type alias, and copy either output.
Paste a sample response. The converter writes TypeScript interfaces and a matching Zod schema side by side, merging every item of an array so fields missing from some items come out optional.
safeParse returns success or a list of issues instead of throwing, which suits fetch code that should show an error state.
import { z } from "zod";
const StaffSchema = z.object({
name: z.string(),
role: z.string(),
startDate: z.string().optional(),
});
const ShopSchema = z.object({
id: z.number(),
name: z.string(),
staff: z.array(StaffSchema),
});
type Shop = z.infer<typeof ShopSchema>;
async function loadShop(url: string): Promise<Shop | null> {
const res = await fetch(url);
const result = ShopSchema.safeParse(await res.json());
if (!result.success) {
console.error(result.error.issues);
return null;
}
return result.data;
}Handy for typing props or function arguments when the data is already trusted.
export interface Shop {
id: number;
name: string;
staff: Staff[];
}
export interface Staff {
name: string;
role: string;
startDate?: string;
}
TypeScript types vanish when your code compiles. They help your editor and catch mistakes in your own code, but they cannot tell you that an API sent a string where you expected a number.
A Zod schema is real code that runs. Calling parse on incoming data throws an error that names the bad field, and z.infer gives you the TypeScript type from the same schema, so the two never drift apart. For data from outside your app, the schema is the safer choice.
A field that was null in your sample becomes the type null, because the sample gives no other clue. Change it to string | null (or .nullable() in Zod) once you know what it holds, or tick the option that makes null fields optional.
An empty array becomes unknown[] for the same reason. Dates arrive in JSON as strings, so they stay strings; in Zod you can tighten one to z.string().datetime() or similar once you know the format.
Type names come from the keys: a staff array produces Staff, an address object produces Address. If two keys would give the same name, a number is added.
Both describe object shapes equally well here. Interfaces with the same name merge, which helps when extending library types; type aliases cannot merge but can name unions. Pick one style for the codebase and stay with it.
Use type User = z.infer<typeof UserSchema>. The generated Zod output already includes this line under every schema, so you do not need to maintain separate interfaces.
Your sample had null there, so there was nothing else to infer. Replace it with the real type joined with null, for example string | null, or z.string().nullable() in the schema.
No. Parsing and generation run in JavaScript on this page. You can load the page, go offline and it still works.
Related: JSON formatter, JSON Schema generator, JSON path finder, JSON diff checker.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.