JSON to TypeScript
Generate TypeScript interfaces from a JSON sample, merging every record so differing keys become optional and varying types become unions.
JSON sample
a real response works bestTypeScript
Paste a JSON sample to generate interfaces.
Inference runs in your browser — the sample is never uploaded. Types describe the sample you provide, so paste a response that includes the optional and null-valued fields you care about.
About the JSON to TypeScript
Paste a JSON response and get TypeScript interfaces for it — nested objects extracted into their own named types, arrays given element types, and optional fields marked where the sample says they are optional. It turns the tedious half-hour of typing out an API's shape by hand into a paste and a copy.
The inference merges every sample it sees rather than reading the first one and assuming the rest match. That distinction is the whole value of the tool. Real payloads are not uniform: one record in an array has a `note` and the next does not, a timestamp is a string in most rows and null in a few. Take the first element as gospel and you get types that compile but lie, and the lie surfaces at runtime. Merging produces `note?: string` and `at: string | null`, which is what the data actually says.
Two conveniences worth knowing about. Structurally identical objects collapse to a single interface, so a billing and a shipping address that share a shape do not become two near-duplicate declarations. And interfaces are emitted in dependency order, so the file compiles as-is without reordering.
One caveat this cannot design around: the output describes your sample, not the API's contract. A field that is nullable in production but happened to be populated in your sample will be typed as non-nullable. Paste a response that includes the awkward cases, and treat the result as a strong first draft to check against the documentation.
- Merges every object in an array, so differing keys become optional rather than being lost
- Unions types that vary between samples, including null
- Structurally identical objects deduplicated into one interface
- Declarations emitted in dependency order, so the output compiles unmodified
- Options for interface or type alias, readonly modifiers, export, and the root type name
How to use it
- Paste a real JSON response into the left pane — the more records it contains, the better the inference.
- Set the root type name to something meaningful, such as the endpoint it came from.
- Choose interface or type. Interfaces are the usual choice for object shapes; type aliases are needed if you want to union the root with something else later.
- Turn on readonly if the data is immutable in your code, and turn off Optional from samples if you know every field is always present.
- Copy the output into your project, then check nullable and optional fields against the API documentation.
Real-world use cases
Frontend developers
Turn an API response copied from DevTools into interfaces for a fetch client or a React Query hook, instead of typing out every nested field by hand and inevitably missing one.
Backend & API developers
Generate a starting type for a response your own service returns, then diff it against the handwritten DTO to catch fields the code emits but the type never declared.
Full-stack developers
Paste a third-party webhook payload to get working types in seconds, rather than reading vendor documentation that is out of date or missing entirely.
QA & test engineers
Generate a type for a fixture so a test's mock data is checked against the same shape the real endpoint produces, catching drift when the API changes.
AI & tooling engineers
Type an LLM API's JSON response — chat completions, tool-call arguments, structured outputs — so the rest of the codebase consumes it with full autocomplete instead of `any`.
Examples
A nested API response
{
"id": "usr_8f2b",
"active": true,
"roles": ["admin", "billing"],
"profile": {
"timezone": "Europe/London",
"locale": null
},
"orders": [
{ "id": 1, "total": 9.99, "note": "gift" },
{ "id": 2, "total": 12.5 }
]
}export interface Profile {
timezone: string;
locale: null;
}
export interface Order {
id: number;
total: number;
note?: string;
}
export interface User {
id: string;
active: boolean;
roles: string[];
profile: Profile;
orders: Order[];
}note is optional because the second order lacks it. Order is singular because the key was orders. Profile and Order are declared before User, so the file compiles top to bottom.
Optional fields from differing records
{
"users": [
{ "id": 1, "name": "Ada" },
{ "id": 2 }
]
}export interface User {
id: number;
name?: string;
}
export interface Response {
users: User[];
}id appears in both records so it stays required; name appears in one, so it becomes optional. Reading only the first element would have produced a required name.
A field whose type varies
{
"rows": [
{ "v": 1 },
{ "v": "x" },
{ "v": null }
]
}export interface Row {
v: string | number | null;
}
export interface Table {
rows: Row[];
}Union members are emitted in a fixed order, so shuffling the input records still produces byte-identical output.
Shared shapes collapse to one interface
{
"billing": { "line1": "1 Main St", "city": "London" },
"shipping": { "line1": "2 Side St", "city": "Leeds" }
}export interface Billing {
line1: string;
city: string;
}
export interface Order {
billing: Billing;
shipping: Billing;
}One declaration rather than two identical ones. Rename it to Address after pasting — the tool names it after the first key it saw.
Common errors
| Message | Cause | Fix |
|---|---|---|
| A field is typed null instead of the type you expected | Every sample had null there, so there is nothing else to infer from. | Paste a sample where the field is populated, or widen it by hand to string | null. This is the most common reason to check the output against the docs. |
| A nullable field is typed as non-nullable | The opposite case — your sample happened to have it populated everywhere. | Include a record where it is null, or add | null yourself. The tool can only describe the bytes you gave it. |
| Object literal may only specify known properties | The generated type is narrower than the real payload, because the sample did not contain every field the API can return. | Paste a fuller sample. Combining several different responses into one array is a quick way to widen the inference. |
| Interface named Datum, Statu or similar | The singulariser applied an English plural rule to a word that is not a plural. | Rename it. Naming is a heuristic and always will be — it is meant to save typing, not to be authoritative. |
| Numbers are typed number when they are really integers or currency | JSON has a single number type; there is nothing in the data distinguishing an integer from a float. | Narrow by hand where it matters. Note that a currency amount sent as a float already has precision problems upstream. |
| Type is Record<string, unknown> instead of an interface | The object was empty in the sample, so there are no keys to infer. | Provide a populated example. An empty object genuinely carries no structural information. |
Frequently asked questions
›Is my JSON uploaded anywhere?
No. Parsing and inference both run in your browser. API responses, customer records and tokens in the payload never leave the page, and nothing is stored or logged.
›How does it decide a field is optional?
By counting. Every object at the same position is merged, and a key present in some samples but not all becomes optional. A key present in every sample stays required. If your sample has only one record, nothing can be optional — which is why pasting an array of several records gives much better types.
›Can I trust the output as the API's contract?
Treat it as a well-informed first draft. It describes exactly the sample you pasted, so a field that is nullable in production but populated in your sample will be typed as non-nullable. Check optional and nullable fields against the documentation, and prefer a sample that includes the edge cases.
›Why interfaces rather than type aliases?
For object shapes they are near-equivalent, and interfaces give slightly better editor output and support declaration merging. Type aliases are the right choice when you later need to union the root with something else, or use mapped and conditional types — so both are offered.
›How are the interfaces named?
From the key that holds them, converted to PascalCase, with array element names singularised — so orders: [...] yields an Order interface. It is a heuristic, and it will occasionally produce an odd name for an irregular plural. Renaming after pasting is expected.
›Should I use this or a schema-driven generator?
If the API publishes an OpenAPI or JSON Schema document, generate from that — it is the actual contract, including fields your sample never contained. This tool is for the very common case where no schema exists, or where you have a response in front of you and want types in the next ten seconds.
›Why is the same output produced every time?
Union members are emitted in a fixed order and declarations are ordered by dependency, so identical input always produces byte-identical output. That matters if the generated file is checked into a repository — regenerating will not create a spurious diff.
›Does it handle dates, or type them as strings?
As strings, and deliberately so. A JSON value with no ISO-8601-looking content has no reliable signal that it is a date rather than an arbitrary string, and guessing wrong is worse than leaving it alone. If a field is a timestamp, narrow it by hand — to string and a runtime `new Date(...)` call at the boundary, or to a branded type, depending on your project's convention.
Last updated