Generate TypeScript Types from JSON — the Right Way
Generating types from a sample response takes seconds. Getting types you can trust takes a few more decisions: what’s optional, what’s nullable, how to name things, and how to make sure the API keeps its promise at runtime.
Start from real samples
Paste a response into the JSON to TypeScript converter and you get interfaces for every nested object. One sample shows one shape, though, so paste an array of several real objects: the converter merges them and marks fields that are missing from some as optional.
[
{ "id": 1, "name": "Ada", "email": "ada@example.com", "team": { "id": 7, "name": "Core" } },
{ "id": 2, "name": "Grace", "email": null }
]
interface User {
id: number;
name: string;
email: string | null;
team?: Team;
}
interface Team {
id: number;
name: string;
}
Optional is not nullable
team?: Team means the key may be missing. email: string | null means the key is always there but may be null. APIs use them differently and your code checks them differently ('team' in user vs user.email === null), so keep the distinction instead of making everything ?: … | null.
Tighten what the sample can’t tell you
- Literal unions — a
statusfield seen as"active"becomesstring; replace it with'active' | 'paused' | 'deleted'from the API docs. - Dates arrive as ISO strings. Type them as
stringand parse at the edge — don’t pretend they areDateobjects. - IDs — large numeric IDs (over 253) lose precision in JavaScript; many APIs send them as strings for that reason.
- Empty arrays become
unknown[]orany[]; fill in the element type yourself.
Types disappear at runtime
TypeScript checks your code, not the data. If the API changes, the compiler won’t notice. Validate responses at the boundary:
- Generate a schema from the same samples with JSON to JSON Schema.
- Edit it — required fields, enums, formats like
date-time— and test real payloads in the JSON Schema Validator. - Validate in your app with a library such as Ajv, and generate types from the schema (JSON Schema to TypeScript) so there is one source of truth.
import Ajv from 'ajv';
const ajv = new Ajv();
const validate = ajv.compile(userSchema);
const data = await res.json();
if (!validate(data)) throw new Error('Unexpected API response: ' + ajv.errorsText(validate.errors));
Libraries like Zod or Valibot do the same with the schema written in TypeScript itself, and infer the type from it.
Naming and structure
Give the root type a meaningful name (User, not Root), export the types from one module, and prefer interface for object shapes you may extend. Format and inspect large responses first with the JSON Formatter so you can see which branches matter.