Typing API responses by hand is slow and error-prone. You copy a payload, write an interface, miss the optional field that only appears sometimes, and find out in production. Generating the types from real data is faster and catches those cases early.
The quick way
- Copy a sample response, from Postman, the browser’s network tab or
curl. - Paste it into JSON to TypeScript.
- Choose interface, type, or JSON Schema, and set the root name.
- Copy the output into your project.
For example, this payload:
{
"id": 17,
"name": "Ada",
"email": null,
"roles": ["admin", "editor"],
"address": { "city": "Leeds", "postcode": "LS1 1AA" }
}
produces:
interface Root {
id: number;
name: string;
email: null;
roles: string[];
address: Address;
}
interface Address {
city: string;
postcode: string;
}
Nested objects become their own named interfaces, which keeps the output readable and lets you reuse the types.
Give the generator a good sample
A type can only be as good as the data it was inferred from. Three things help:
Use several records. Paste an array of items, not one. When some items have a field and others don’t, the generator can see that the field is optional and mark it field?: string. With a single record, every field looks required.
Include variety. If a field is sometimes null or sometimes a string, show both. Otherwise the type will be the narrower of the two. A field that is null in your only sample is typed as null, which is rarely what you want.
Use realistic data. An empty array [] tells the generator nothing about its items, so it becomes unknown[] or any[]. Replace it with a real element before generating.
Modelling choices
Optional versus nullable
email?: string means the key might be absent. email: string | null means the key is always present but may hold null. APIs mix both, and they are different in code:
user.email?.toLowerCase() // fine for both
"email" in user // only distinguishes the optional case
Check your API documentation if you can, and adjust the generated types by hand where the sample could not show the difference.
Unions
When a field holds different types in different records, such as id being number in some and string in others, the correct type is a union: id: number | string. That is usually a sign of an inconsistent API. You may prefer to normalise it at the boundary rather than carry the union through your code.
Dates
JSON dates are strings. A generator will type them string, which is correct but not helpful. Consider a named alias for clarity: type ISODate = string;. Convert to Date objects explicitly after parsing.
Large numbers
Integers above 2^53 cannot be represented exactly as a JavaScript number. If an ID is 19 digits long, type it as string and make sure your parser does not round it.
Dynamic keys
Objects used as dictionaries, such as {"2024-01": 5, "2024-02": 9}, are better typed as Record<string, number> than as an interface with one property per key. If the generated type has dozens of similar properties, replace it.
Interface or type?
For object shapes they are almost interchangeable. Interfaces can be extended and merged, and have slightly clearer error messages; type aliases can express unions and mapped types. Use whichever your codebase already prefers. For one-off response shapes, type and interface both work.
JSON Schema output
The same sample can produce a JSON Schema document, which is useful for:
- Runtime validation. Libraries such as Ajv validate data against a schema.
- Documentation. OpenAPI uses JSON Schema for its payload definitions.
- Contracts. Share the schema between front-end and back-end teams.
Types vanish at runtime, so TypeScript alone will not protect you from a bad response. If the data comes from outside your code, validate it, then trust the types.
After generating
-
Review the output. Treat it as a draft and fix the optionals and unions the sample couldn’t show.
-
Rename types.
RootandAddressare fine;Item2is not. Name them for the domain. -
Put them in one file such as
types/api.ts, and import them where you parse responses. -
Use them at the boundary:
const res = await fetch("/api/user"); const user = (await res.json()) as User;The
ascast tells the compiler to trust you. For anything you don’t control, validate first. -
Regenerate when the API changes. Keep a sample file in the repo so it is easy.
Troubleshooting
Invalid JSON. The generator needs valid input. Use the JSON Formatter to find the problem; see common JSON errors.
Everything is any or unknown. Your sample probably has empty arrays or null values. Add realistic data.
Property names aren’t valid identifiers. Keys like first-name are quoted in the output: "first-name": string. You can also map them to camelCase when you parse.
Related guides
Types describe shape; to inspect actual values at scale, view the response as a table. To pull a typed slice out of a large payload, use JSONPath first. If you are storing the data, see generating SQL from JSON.