Polyatic JSON to TypeScript

JSON to TypeScript

Paste a JSON sample and get TypeScript interfaces — or a Zod v3 schema — generated live. Nested objects become their own named interfaces; nothing is uploaded.

Paste or type JSON

Types are inferred from this one sample. Arrays follow their first element; null stays null; empty arrays become unknown[].

Waiting for JSON…
TypeScript

        

        

Runs entirely in your browser — your JSON never leaves your device. Parsing uses the built-in JSON engine and the type generator is a small script you can read; there are no network requests and no analytics on this tool.

What this tool does

Paste any JSON value into the box on the left and a set of TypeScript definitions appears on the right the instant you stop typing — no button to press. A top-level object becomes an interface Root; every nested object is pulled out into its own named interface, and the root simply references it by name. The result is copy-paste-ready types you can drop into a .ts file to replace the tedious hand-typing that usually follows “here is a sample API response, now write the types.”

How types are inferred

The rules are intentionally small and predictable, so you always know why a given type came out the way it did:

  • Primitives map straight across: a JSON string becomes string, a number becomes number (TypeScript has no int/float split), and true/false become boolean.
  • Objects each become their own interface, named after the key holding them in PascalCase — a billingAddress object yields interface BillingAddress. Duplicate names get a numeric suffix (Address, Address2) so nothing collides.
  • Arrays are typed from their first element: [1,2,3]number[], and an array of objects → Thing[] with a matching interface. An empty array can’t be inferred, so it becomes unknown[].
  • null keeps the property but types it null, since one sample can’t reveal the non-null shape.
  • Anything the parser can’t place falls back to any, which never happens for valid JSON but guards odd edge cases.

Worked example

This JSON — a small product record with a nested object, an array of objects and a null —

{
  "id": 42,
  "name": "Widget",
  "tags": ["new", "sale"],
  "dimensions": { "width": 10, "unit": "cm" },
  "variants": [{ "sku": "W-01", "color": "red" }],
  "discontinuedAt": null
}

generates three interfaces — the root, the nested dimensions object, and the shape of the first variants element (its key singularised to Variant):

interface Root {
  id: number;
  name: string;
  tags: string[];
  dimensions: Dimensions;
  variants: Variant[];
  discontinuedAt: null;
}

interface Dimensions {
  width: number;
  unit: string;
}

interface Variant {
  sku: string;
  color: string;
}

Notice the honest gaps you would fix by hand: discontinuedAt is really string | null, and if some products carry more variant fields than the first one, Variant needs those added. The tool does the mechanical 90% and leaves those judgement calls to you.

Zod schema output

Flip the toggle above the result from TypeScript interface to Zod schema and the very same inferred shape is printed as a Zod v3 schema instead. There is only one inference pass behind both buttons, so the interface and the schema always describe the identical structure — the toggle just changes how that one model is written out:

  • an object becomes z.object({ … }) with nested objects inlined as nested z.object;
  • primitives map to z.string(), z.number(), z.boolean(); null becomes z.null(); an empty array becomes z.array(z.unknown());
  • an array becomes z.array(…); a value that varies in type across an array becomes z.union([…]);
  • a key present in only some elements of an object array is marked .optional() — the same “a key you actually saw is required” rule, applied across the merged elements.

The output is a runnable export const Root = z.object(…) plus a matching z.infer type alias, ready to paste next to your data. Treat it as a starting point. A schema built from one sample validates shape and type but not meaning: it cannot know an email string needs .email(), an id needs .uuid(), a quantity needs .int().nonnegative(), or that a status is really an enum. Add those refinements by hand — the tool spares you the z.object scaffolding, you supply the rules only you know. It targets Zod v3 syntax; Zod 4 keeps these builders, so the output ports with little or no change.

Honest limits

This is a single-sample inferrer, not a schema analyser, and it is deliberately upfront about what that means. It reads one value, so it cannot see optional properties (a field missing from some records), union types (a value that is sometimes a string and sometimes a number), or the true element type of a heterogeneous array — it trusts the first element and moves on. It does not merge multiple samples, deduplicate structurally-identical interfaces, or detect that two differently-named objects share a shape. Numbers all become number even where you might want a string literal union or a branded type, and dates arrive as string because JSON has no date type. For those cases, treat the output as a fast first draft: paste, copy, then widen nulls, mark optionals with ?, and merge duplicate shapes to taste. What you save is the error-prone transcription of a big nested payload into curly braces and semicolons.

Why generate types on-device

Everything here runs locally: the page loads no third-party scripts and makes no network calls, so a production API response, an internal config or a customer record you paste stays in the tab and is gone when you close it. Many online “JSON to TypeScript” converters POST your paste to a server to do the work — this one never does, which matters when the sample you are modelling is real, private data rather than a toy example.