How to convert JSON to TypeScript
- Paste JSON, or load it with Open file or From URL. Conversion runs in your browser as you type.
- Name the top-level type in Root name.
- Pick Interfaces or Type aliases under Style.
Example: a GitHub user
With Root name set to User, the sample becomes:
{
"login": "octocat",
"id": 583231,
"avatar_url": "https://avatars.githubusercontent.com/u/583231",
"type": "User",
"site_admin": false,
"name": "The Octocat",
"company": "@github",
"blog": null,
"public_repos": 8,
"followers": 21000,
"plan": { "name": "pro", "collaborators": 0 },
"created_at": "2011-01-25T18:44:36Z"
}
export interface User {
login: string;
id: number;
avatar_url: string;
type: string;
site_admin: boolean;
name: string;
company: string;
blog: unknown | null;
public_repos: number;
followers: number;
plan: Plan;
created_at: string;
}
export interface Plan {
name: string;
collaborators: number;
}
Keys keep their JSON spelling (avatar_url, not avatarUrl), so the interface matches what res.json() returns. blog is null in the sample, which says nothing about its real type, so it comes out as unknown | null; change it to string | null.
How the types are inferred
Every generator on this site, from Go to Python, follows these rules:
- Array items are merged. A list of 50 orders gives one
Ordertype that fits all 50. - Missing keys become optional (
?). nullmakes a field nullable (| null). A key that is only evernullbecomesunknown | null.- Numbers widen.
3in one item and1.5in another merge into a decimal:numberhere,float64in Go. - Different types become a union, such as
string | number. Languages without unions use a catch-all type, such asanyin Go orobjectin C#. - Empty arrays become
unknown[]. - Identical shapes share one type.
- Names come from keys.
billing_addressbecomesBillingAddress, array keys turn singular (categoriesgivesCategory), and when two different shapes want one name, the second is prefixed with its parent’s name.
With Issues as the root name, this input shows most of them:
[
{
"id": 41,
"title": "Crash on empty input",
"user": { "login": "ada" },
"assignee": { "login": "lin" },
"labels": [],
"points": 3
},
{
"id": 42,
"title": "Add dark mode",
"user": { "login": "sam" },
"assignee": null,
"labels": [],
"points": 1.5,
"milestone": "v2.1"
}
]
export type Issues = Issue[];
export interface Issue {
id: number;
title: string;
user: User;
assignee: User | null;
labels: unknown[];
points: number;
milestone?: string;
}
export interface User {
login: string;
}
assignee has the same shape as user, so both use User, and its null makes it User | null. milestone is in one issue only, so it is optional, and the always-empty labels stays unknown[]. Paste several real items rather than one, and fewer of these guesses need fixing.
Interface or type alias
For object shapes the two are interchangeable; the output differs only in the keyword. An interface can be extended or reopened by a second declaration, a type alias cannot. The typescript-eslint rule consistent-type-definitions prefers interfaces by default, and so does this tool.
Using the generated types
async function getUser(url: string): Promise<User> {
const res = await fetch(url);
return (await res.json()) as User;
}
The cast is a promise to the compiler, not a check: types vanish at runtime, so an API change breaks silently. For data you don’t control, build a validator from the same sample with JSON to Zod, or describe the contract with the JSON Schema generator. If the JSON won’t parse, the JSON validator shows the line and column.