Generate TypeScript Interfaces from JSON (The Complete Guide)
Typing an API response by hand is tedious and easy to get wrong: you squint at a payload, guess at which fields are optional, and forget half of them by the time you reach the nested objects. Generating the TypeScript interfaces straight from a real JSON sample removes the guesswork, and the types match the data exactly. This guide covers how that generation works, the fastest way to do it, how inference handles the tricky cases, and the gotchas to watch for.
The fastest route: a browser generator
For a one-off, paste the JSON into the JSON to TypeScript converter. It reads the sample, infers the type of every field, and writes clean interfaces: nested objects become their own interfaces, arrays are typed by their element, and optional fields and unions are detected automatically. You name the root interface and choose whether to add export. Because it runs entirely in your browser, the JSON never leaves your machine, which matters when the sample is a real production response.
If the JSON will not convert, it is almost always invalid rather than genuinely unconvertible. Run it through the JSON parser to find the exact syntax error, or format it so you can see the structure first.
How the inference works
A generator walks the JSON and makes the same handful of decisions every time. Knowing them tells you what to expect, and why.
- Objects become interfaces. Each object turns into a named
interface, and each key becomes a field typed by its value. - Nested objects get their own interface. Rather than inlining a deep object, the generator names it and references it from the parent, which keeps the output readable and reusable.
- Arrays are typed by their element.
["a","b"]becomesstring[]; an array of objects becomesItem[]with a generatedIteminterface. - Primitives map directly.
string,number, andbooleanmap to the matching TypeScript types.
Here is the shape of a simple conversion:
{ "id": 1, "name": "Ada", "tags": ["admin", "dev"] }
becomes:
export interface Root {
id: number;
name: string;
tags: string[];
}
Optional fields: the part that matters most
The single most useful thing a generator does is detect optional fields, and it can only do that from an array of objects. When several objects share a shape but some omit a key, that key is optional:
[
{ "sku": "J-01", "qty": 2 },
{ "sku": "J-07" }
]
export interface Root {
sku: string;
qty?: number;
}
qty is marked optional with ? because the second record omits it. This is why you should paste a representative sample with several records, not a single object: one object alone cannot reveal which fields are sometimes missing. The more varied the sample, the more accurate the interface.
Null and union types
JSON has no way to say “string or null”, so a generator infers it from the data. A field that is null in the sample is typed null; a field that is sometimes a string and sometimes null becomes a union:
coupon: string | null;
The same logic applies to arrays of mixed types. [1, "a"] becomes (number | string)[]. Unions keep the interface honest: it describes every value actually seen, rather than silently picking one.
When you see any
An empty array or object gives the generator nothing to infer from, so it falls back to any[] or any. That is not a bug, it is a lack of evidence. The fix is to paste a sample where those fields are populated. If a field is genuinely freeform, any (or unknown, which you can tighten to by hand) is the honest type. Expanding the payload in the JSON viewer first helps you spot which fields come back empty.
Doing it with libraries
For a one-off, the browser tool is quickest. For a repeatable build step, two approaches are common:
quicktypegenerates types from JSON (and JSON Schema) for TypeScript and many other languages, and runs as a CLI or library. It is the most full-featured option and handles large, messy samples well.json-schema-to-typescriptis the right tool when you already have a JSON Schema: it compiles the schema to interfaces, so the types track your schema as the source of truth.
A rule of thumb: generate from a sample when the shape is defined only by example data, and generate from a schema when you have one, since a schema encodes optionality and constraints that a single sample cannot.
The gotchas that bite
- One sample hides optionality. A single object makes every field look required. Feed an array of several records so the generator can spot missing keys.
- Over-trusting inferred numbers. If an ID arrives as a JSON number but can exceed
Number.MAX_SAFE_INTEGER, TypeScript typing itnumberwill not save you from precision loss at runtime. Keep large IDs as strings in the JSON. - Enums look like plain strings. A field that is really one of a few values (
"status": "active") infers asstring. Tighten it to a union ("active" | "archived") by hand if it matters. - Generated names can collide. Two different nested objects named
dataneed distinct interface names; good generators suffix them, but check the output. nullvs optional are different.qty?: numbermeans the key may be absent;qty: number | nullmeans it is present but may be null. They are not interchangeable, and the generator reflects whichever the data showed.
Frequently asked questions
How do I generate TypeScript interfaces from JSON?
Paste the JSON into the JSON to TypeScript converter and it writes the interfaces, inferring nested types, arrays, optional fields, and unions. For a build step, quicktype does the same from the command line.
How does it know which fields are optional?
From an array of objects: a key present in some records but missing from others is marked optional with ?. A single object cannot show optionality, so always paste a representative, multi-record sample.
What happens to null values?
A null is typed null, and a field that is sometimes a value and sometimes null becomes a union such as string | null, so the interface matches every value in the sample.
Should I generate from JSON or from a JSON Schema?
Generate from a sample when example data is all you have; generate from a schema (with json-schema-to-typescript) when you have one, because a schema encodes optionality and constraints a single sample cannot.
For the format underneath all this, see the guide on what JSON is, the practical parse JSON in JavaScript walkthrough, and the roundup of free JSON tools for the rest of the toolkit.
Leave a Reply