Why generate types from a sample?
Every API integration starts with the same half hour: someone pastes a JSON response into an editor and hand-writes the TypeScript interfaces that describe it, field by field, guessing at nesting, missing a key that only appears on Tuesdays. A sample is evidence — often the only specification an API actually ships — and the shape of that evidence can be extracted mechanically, completely and without typos. That is what this converter does: paste a representative JSON document and get back the interfaces it implies, ready to paste into your project and argue with. The argument is the valuable part. Generated types are a faithful report of one sample, and the places where they are wrong — an ID that arrived as a number in this response but is a string in another, a field that happened to be null today — are exactly the places where your knowledge of the domain has to take over.
How to use this tool
- Paste the JSON. A real API response is ideal; the more representative the sample — including its edge cases, empty arrays and optional blocks — the more truthful the types.
- Name the root.
Rootis fine for a quick look; for code you will keep, use the name the type will wear in your project (UserProfile,OrderResponse). - Press Generate TypeScript. The root interface comes first, the interfaces it references after it.
- Read the optionality. Any field marked
?was absent from at least one element of an array in your sample — that is your sample telling you the field cannot be trusted to exist. - Copy or download the result, then tighten it: add the unions, formats and semantics only you know about.
The rules it follows
Naming. Interfaces are PascalCase, derived from
the key that holds the object: billing_address
becomes BillingAddress, an array under
line_items becomes LineItem. Keys that
are not legal identifiers (they contain spaces, dashes, or start
with a digit) are quoted in the output, exactly as TypeScript
requires. Merging. All elements of an array are
treated as sightings of one shape: their keys are unioned, the
types for each key are unioned, and absence is recorded as
optionality. Deduplication. Two objects with the
same keys and the same types — wherever they appear in the
document — become one interface with one name, referenced from
both places; your address object in billing and
shipping is one Address, not two copies
waiting to drift apart. Honesty about types.
JSON's six types map to TypeScript's directly: strings stay
string even when they look like dates, numbers stay
number, and an empty array is
unknown[] because the sample offers no evidence.
Worked example
Given this sample (with one user who has no nickname):
{
"team": "Platform",
"users": [
{ "id": 1, "name": "Ada", "nickname": "ada", "admin": true },
{ "id": 2, "name": "Grace", "admin": false }
]
} the converter emits the root first and the merged element shape after it:
export interface Root {
team: string;
users: User[];
}
export interface User {
id: number;
name: string;
nickname?: string;
admin: boolean;
}
Note what the second user taught it: nickname is
optional because the data — not a guess — showed it missing.
Feed it a sample where every user has one and the ?
disappears. The types are only ever as true as the sample you
give them, which is the strongest argument for generating from
the ugliest real response you can find.