JSON to TypeScript
Generate TypeScript interfaces from a sample JSON document, with nested types, arrays and optional fields inferred.
How to use this tool
Steps for using the JSON to TypeScript.
- Paste a representative JSON sample — ideally a real API response — into the Sample JSON panel, or click Sample.
- Set the Root type name (default
Root), and choose whether to declare types asinterfaceortypealiases. - Toggle export and readonly to match your project's conventions.
- Click Generate TypeScript. If the JSON is invalid, the error banner shows the line and column to fix.
- Review the result — especially optional fields and unions — then Copy it or Download it as
types.ts.
Example
Nested objects and an array of objects
{
"id": 42,
"name": "Ada Lovelace",
"email": "ada@example.com",
"active": true,
"manager": null,
"address": { "street": "12 Analytical Way", "city": "London", "postcode": "N1 9GU" },
"tags": ["admin", "beta"],
"projects": [
{ "id": 1, "title": "Difference Engine", "stars": 120 },
{ "id": 2, "title": "Notes on the Analytical Engine", "stars": 87, "archived": true }
],
"lastLogin": "2024-05-01T09:30:00Z"
}export interface Root {
id: number;
name: string;
email: string;
active: boolean;
manager: null;
address: Address;
tags: string[];
projects: Project[];
lastLogin: string;
}
export interface Address {
street: string;
city: string;
postcode: string;
}
export interface Project {
id: number;
title: string;
stars: number;
archived?: boolean;
}Common use cases
- Typing a REST or GraphQL response before writing the code that consumes it.
- Creating interfaces for JSON config files and fixtures used in tests.
- Getting a first draft of types for a third-party API that ships no TypeScript definitions.
- Spotting inconsistencies in an API payload — unexpected unions or optional fields reveal where the data varies.
- Teaching or reviewing how a JSON structure maps onto TypeScript types.
How types are inferred from a JSON sample
The generator walks the parsed JSON and maps each value to a TypeScript type: strings, numbers and booleans to string, number and boolean, null to null, arrays to T[] and objects to named interfaces. Every nested object becomes its own declaration named after its key in PascalCase — billing_address becomes BillingAddress — and array items use a singular form of the key, so projects produces Project[] and categories produces Category[]. Keys that are not valid identifiers, such as first-name or 2fa, are quoted.
Arrays are where most inference happens. All elements are merged into one element type: if every element is an object, their keys are combined and any key missing from at least one element is marked optional with ?. Mixed primitive values become a union such as (string | number)[], and a field that is null in some elements and a string in others becomes string | null. An empty array carries no information about its elements, so it becomes unknown[] — unless another sample of the same field has items, in which case those win.
Structurally identical objects share a single declaration: if home and work are both { city: string }, you get one Home interface used twice rather than two copies. When two different shapes would get the same name — two unrelated meta objects, say — the second is suffixed (Meta2) so nothing collides. Names that would shadow built-in globals like Error, Date or Response get a Type suffix.
Inference can only describe the sample you give it. A field that happens to be null in the sample is typed as null, a string that holds a date stays string, and a key that is always present in your sample is required even if the API sometimes omits it. Paste a sample that exercises the variations you care about — several array elements are better than one — and treat the output as a starting point to refine with your API's documentation.
Limitations
- Only the sample is analysed: values that are always
nullbecomenull, and fields absent from the sample are not generated. - Dates, UUIDs, enums and other string formats are typed as plain
string; numbers are never narrowed to literal types. - Objects with dynamic keys (maps keyed by ID) produce an interface with those specific keys rather than
Record<string, T>. - Singularisation handles common English plurals only; unusual names fall back to an
Itemsuffix. - No runtime validation code is generated — pair the types with a schema validator if you need runtime checks.
Frequently asked questions
Should I choose interface or type?
For object shapes they are nearly interchangeable. Interfaces can be extended and merged by declaration, and their names show up in error messages; type aliases can also express unions and mapped types. Many codebases standardise on one — pick whatever your linter or team prefers.
Why is a field typed as null instead of string | null?
Because every occurrence in your sample is null, so there is nothing else to infer. Include an example where the field has a value, or edit the generated type by hand.
How are optional properties decided?
Within an array of objects, a key that is missing from at least one element is marked optional with ?. A key that is present but null is not optional — it gets | null instead, which matches how JSON distinguishes a missing key from a null value.
Is my JSON uploaded?
No. Parsing and type generation run in your browser and nothing is sent to a server. You can format or validate the sample first with the JSON Formatter.
What does the readonly option do?
It prefixes every property with readonly, so TypeScript reports an error if code assigns to it. It applies to properties only; arrays stay mutable T[] types.