Generating TypeScript types from JSON
Generating a type from a JSON sample takes seconds and removes a lot of typing. Turning that draft into a type you can rely on takes a little more thought.
From response to type
Suppose an API returns this user:
{
"id": 42,
"name": "Ada",
"email": "ada@example.com",
"roles": ["admin", "editor"],
"manager": null,
"createdAt": "2026-03-14T10:00:00Z"
}Pasting it into the JSON to TypeScript generator produces:
export type Root = {
id: number;
name: string;
email: string;
roles: (string)[];
manager: null;
createdAt: string;
};That is already useful. Your editor can now autocomplete user.roles, and the compiler will warn you if you misspell email. Rename Root to something meaningful such as User, and move nested object types into their own named types if they are reused.
What a single sample cannot tell you
The generator sees one example of the data, not the rules the server follows. Review each property with these questions in mind.
Can the value be null?
manager is null in the sample, so the generated type is null, which would reject a real manager. If the API documentation says it is a user ID or null, change it to manager: number | null. The opposite also happens: a field that contains a string in your sample may be null for other records.
Is the property always present?
If some responses omit a field entirely, mark it optional with a question mark: nickname?: string. A sample cannot reveal which fields are optional, because a missing field simply does not appear. Pasting two different responses into a JSON diff is a quick way to discover fields that appear only sometimes.
Is it really a free-form string?
roles is typed as an array of strings, but the API may only allow a fixed set of values. A union of string literals is more precise and catches typos:
type Role = "admin" | "editor" | "viewer";
roles: Role[];The same applies to status fields such as "pending" | "paid" | "refunded".
What about dates?
JSON has no date type, so createdAt arrives as a string. Keep it typed as string in the response type and convert it with new Date(value) where you need a date object. Typing it as Date would be incorrect, because no conversion happens automatically when you call response.json().
Empty arrays and mixed arrays
An empty array in the sample becomes (unknown)[], because there is no element to inspect. Replace unknown with the real element type. An array that contains different kinds of objects produces a union of object types; decide whether that reflects reality or whether the objects should share one type with optional fields.
Large numbers
JavaScript numbers lose precision above 2^53 − 1. If an API returns 64-bit identifiers as numbers, the values may already be rounded when parsed. Prefer APIs that send such IDs as strings, and type them as string.
Types are erased at runtime
TypeScript checks your code, not your data. When you write const user = (await res.json()) as User, the compiler trusts you; if the server returns something different, the mistake appears later as undefined in an unexpected place. For data that crosses a trust boundary, such as API responses, user uploads and messages from other services, validate it at runtime.
Common options are schema libraries such as Zod, Valibot or io-ts, where you write a schema and derive the TypeScript type from it, or JSON Schema validators such as Ajv. With a schema library the schema becomes the single source of truth: the type cannot drift away from the validation rules. A generated type is still a helpful starting point, because it shows you every property you need to describe.
A practical workflow
- Collect two or three real responses, including an edge case such as a user with no manager.
- Generate a type from each sample and compare the results.
- Merge them into one reviewed type: add
| null, optional markers and literal unions. - Name nested types and place them in a shared module.
- Add runtime validation where the data comes from outside your application.
- Regenerate and compare when the API version changes, so you notice new or removed fields.
If a sample does not parse, check it first with the JSON formatter; generated types are only as good as the sample behind them.