How to Generate TypeScript Types from JSON (2026)

⌨️ ~5K searches/mo💰 CPC: $1⏱️ 8 min read

The fastest honest way to type an API: paste a real payload, get interfaces back, and tighten by hand. This guide covers the inference rules that matter — unions for mixed arrays, null handling, merged array-of-object shapes — plus the three places a sample can't tell the truth, so your generated types don't turn into runtime surprises.

Advertisement

Why generate types from a sample at all?

Hand-writing an interface from API docs fails quietly: docs lag, fields get renamed, and optional things turn out to be conditional. Typing from a curl response flips the source of truth — the types describe data that provably existed. It's the same instinct as generating database types from a live schema instead of an ER diagram someone drew in 2022.

The catch is that one sample is evidence, not a contract. Generation gets you 90% of the way in seconds; the last 10% (optionality, enums, dates) still needs a human. Used that way, sample-to-type is a productivity tool. Used as a contract, it's a bug generator.

What are the inference rules?

JSONTypeScriptWhy
"x" / 1 / truestring / number / booleanDirect mapping
nullnullJSON has no undefined; hiding nulls invites crashes
["a","b"]string[]One element type wins
[1,"two"](number | string)[]Union of distinct types, sorted for stable output
[{"a":1},{"b":"x"}]one merged Item interfaceFields union per key; that's the shape you program against
[]unknown[]An empty array proves nothing about elements
nested objectnamed interface per pathaddress under Root becomes RootAddress

The merged-array rule deserves a note, because it's where naive converters differ. Given two orders where one has a discount field and the other doesn't, the honest generated type unions what exists — discount?: ... territory — rather than emitting two near-identical interfaces you'd have to handle separately at every call site. Merge-first keeps the type ergonomics of one array, one item type.

Worked example: one payload, two outputs

{
  "id": 1,
  "name": "Ada Lovelace",
  "active": true,
  "score": null,
  "tags": ["math", "pioneer"],
  "address": { "street": "12 Analytical Way", "city": "London", "zip": null },
  "orders": [
    { "id": 101, "total": 59.99, "items": ["book", "slide rule"] }
  ]
}

With strict nulls kept, generation produces three interfaces:

interface Root {
  id: number;
  name: string;
  active: boolean;
  score: null;
  tags: string[];
  address: RootAddress;
  orders: RootOrdersItem[];
}

interface RootAddress {
  street: string;
  city: string;
  zip: null;
}

interface RootOrdersItem {
  id: number;
  total: number;
  items: string[];
}

Switch strict nulls off and the two null fields become any — which is exactly what your types behave like in a codebase that doesn't run strictNullChecks. Prefer keeping nulls and letting the compiler force the ?? fallback; deleting them from the type doesn't delete them from the payload.

Generate types from your payload

Paste JSON, pick a root name, copy the interfaces. Runs 100% client-side — your payload never leaves the browser.

JSON to TypeScript Converter →

Where can a JSON sample lie?

  1. Optionality. A field missing from the sample might be absent always, sometimes, or only in this response. Sample generation can't know. Mark ?: by hand from docs or multiple samples.
  2. Enums in disguise. "status": "active" looks like string but is really "active" | "paused" | "closed". One sample shows one value. Tighten these manually — they're also the fields most likely to break your UI when a new value ships.
  3. Dates and IDs. ISO strings and numeric-looking identifiers both infer as their primitive. Type them as string and parse at the boundary, or brand them (type UserId = string & {__brand: 'UserId'}) so a UserId can't be passed where a PostId goes.

How does this fit a real workflow?

Three patterns, in increasing order of rigor. First, one-off: paste a payload, copy the types, adjust. Second, fixture-driven: keep a __fixtures__ directory of real responses, regenerate on demand, and diff the output in code review — type churn becomes visible, which is the whole point. Third, schema-driven: if the backend publishes OpenAPI or JSON Schema, generate from that instead, because a schema states optionality and enums where a sample can't. The sample-first tools still win for third-party APIs that document with a shrug.

Whatever the pattern, keep generated files clearly marked and mechanically produced. The moment someone hand-edits a generated interface, regeneration becomes a conflict, and the file rots. For the surrounding workflow, the JSON formatter cleans up minified payloads before conversion, and the JSON schema validator is the right next stop when you're ready to move from samples to a real contract.

Is the client-side thing a gimmick?

It's a privacy boundary, and occasionally a hard requirement. API payloads routinely contain real user data, tokens, or internal identifiers, and pasting those into a random web converter means shipping them to whoever runs it. A converter that parses and generates in the browser with no network call keeps the payload on the machine it came from. Our converter works that way, and its engine is Apache-2.0, so you can also lift the generation logic into a script for CI or codegen steps.

Frequently Asked Questions

Can TypeScript infer types from JSON automatically?

Not from a raw string. A JSON string is just string to the compiler. You either generate types from a sample (paste it into a converter), from a JSON Schema, or cast with a type assertion — which skips checking entirely. Generation from a real payload is the middle ground: the types describe actual data instead of a guess.

What is the difference between sample-based types and JSON Schema?

A sample shows what one response looked like; a schema states what every valid response may look like — required versus optional fields, enums, ranges, formats. Sample generation gives you shapes fast but cannot prove a field is optional or constrain a value. If you have a schema, validate against it; if you only have examples, generate from the fattest example you can find and tighten by hand.

How do you type optional fields from a JSON sample?

You can't infer them from one sample — absence in a sample doesn't prove a field can be absent. The practical workflow: generate from a sample, then mark fields optional (name?: string) where the API docs or several samples say they come and go. Feeding several payloads into the converter helps: fields that appear in one array element and not another get unioned, which is the hint.

Should generated types use interface or type?

For object shapes they compile the same. Interfaces allow declaration merging, so you can extend a generated Root elsewhere without editing the generated file — handy when the file is regenerated. Type aliases can express unions, tuples and primitives. A common convention: interfaces for object payloads, aliases for the top-level when the root is an array or primitive.

Related Tools