Why generate types from a JSON sample
Typing an API response by hand is slow and drifts out of date. Generating the types from a real payload gives you autocomplete and compile-time checks in minutes: paste the body from your network tab, a webhook delivery or a fixture file, and copy the result into a types.ts. It is also a quick way to understand the shape of an unfamiliar API.
How types are inferred
- Every object becomes an exported interface. Nested objects get their own interface, named after the key:
addressbecomesAddress, and items of anordersarray becomeOrder. - Strings, numbers and booleans map to
string,numberandboolean. Strings are never narrowed into dates, enums or UUIDs, because a single sample cannot prove that. - Arrays become
T[]. When the items have different types, you get a union such asArray<number | string>. An empty array isunknown[], since there is nothing to infer from. - All objects in an array are merged into one interface. A key present in only some items becomes optional (
note?: string); a key that isnullin some items becomes a union withnull(nick: null | string). - A key whose only value is
nullis typednull; widen it when you know the real type. - Keys that are not valid identifiers, such as
created-at, are kept as quoted property names, so the interface still matches the JSON exactly.
If the JSON’s root is an array or a plain value, the root type is an alias: export type Root = RootElement[];.
Options
Root type name (default Root) names the top-level type. It is converted to PascalCase, so user profile becomes UserProfile, and a name starting with a digit gets a T prefix.
Declare as chooses between interface (the default) and type alias (export type Address = { ... }). Interfaces can be extended and merged; type aliases are preferred by some style guides and work better when you later combine shapes with & or |. The members are identical either way.
The output is formatted with Prettier.
Numbers, precision and limits
TypeScript has one number type, so integers and decimals both map to it. Integers larger than 2^53 cannot be represented exactly by a JavaScript number; when the sample contains one, a warning suggests switching that field to bigint or string (and parsing it accordingly). See big integers in JSON for strategies.
The generated code contains types only — there is no runtime validation, so data that differs from the sample will not be caught at run time. For validation, generate a schema with JSON to JSON Schema and check payloads against it. A richer sample (several array items, with and without optional keys) yields more accurate optional fields. Duplicate keys in the input are reported as warnings; inference uses the final value.
Examples
User with nested objects
Produces Root, Address and Order interfaces; note is optional because only one order has it.
{
"id": 1042,
"name": "Aisha Tan",
"email": "aisha.tan@example.com",
"active": true,
"tags": ["admin", "beta"],
"address": { "city": "Singapore", "postcode": "018956" },
"orders": [
{ "id": "ord_1", "total": 129.9, "paid": true },
{ "id": "ord_2", "total": 79, "paid": false, "note": "gift" }
]
}export interface Root {
id: number;
name: string;
email: string;
active: boolean;
tags: string[];
address: Address;
orders: Order[];
}
export interface Address {
city: string;
postcode: string;
}
export interface Order {
id: string;
total: number;
paid: boolean;
note?: string;
}
Array root as type aliases
The root becomes export type ProductList = ProductListElement[], discount is number | null and bundle is optional.
[
{ "sku": "KB-104", "price": 129.9, "discount": null },
{ "sku": "MS-220", "price": 79, "discount": 0.15, "bundle": ["MS-221"] }
]export type ProductList = ProductListElement[];
export type ProductListElement = {
sku: string;
price: number;
discount: number | null;
bundle?: string[];
};
Common errors and how to fix them
| Error | Cause | Fix |
|---|---|---|
Some integers are larger than 2^53; they are typed as floating-point numbers. Use a 64-bit or big-integer type if you need exact values.Explained | A warning: the sample contains IDs or amounts beyond JavaScript’s safe integer range. | Change those fields to bigint or string and parse them with a big-number-aware JSON parser. |
Object keys must be in double quotes: found …Explained | The input is a JavaScript object literal rather than JSON. | Quote the keys, or convert the text with JSON5 to JSON first. |
The JSON is nested more than 200 levels deep, too deep to generate types for | The input is extremely deeply nested, often a recursive structure serialised many levels down. | Trim the sample to a few levels; the repeated shape will still be inferred. |
Unexpected '<' where a value was expectedExplained | An HTML error page was pasted instead of the JSON response body. | Check the request in your network tab and copy the actual JSON response. |
Frequently asked questions
Should I choose interface or type alias?
Either works for describing JSON. Interfaces are the common default and support declaration merging; type aliases suit codebases that prefer them or combine types with unions and intersections.
How are optional fields detected?
By comparing all objects that end up in the same type. A key missing from at least one of them is marked optional with ?.
Does it recognise dates or enums?
No. ISO date strings stay string and repeated values do not become enums, because one sample is not enough evidence. Refine those fields by hand.
Is my JSON sent to a server to generate types?
No. The inference engine runs inside your browser, so the payload never leaves your machine.