JSON to TypeScript Converter
Paste a JSON sample and get TypeScript interfaces or type aliases for it — nested objects become their own named types, array elements are merged, and keys missing from some elements become optional.
What this tool does
The converter reads one JSON document and writes TypeScript declarations that describe its shape: an exported root type, plus one named interface (or type) for every distinct kind of nested object. It is a quick way to type an API response or a configuration file without writing the declarations by hand. Everything runs in your browser with the built-in JSON.parse; nothing is sent to a server.
How to use it
- Paste JSON into the left pane, or load it with File or URL.
- Optionally change Root type name and choose Declare as
interfaceortype. - Click Convert. The declarations appear in the right pane.
- Copy the result or Download it as
types.ts. Clear empties both panes.
Options
- Root type name (default
Root) names the type for the whole document. It is converted to PascalCase, soapi responsebecomesApiResponse; an empty box falls back toRoot. - Declare as
interfacewritesexport interface Owner { … };typewritesexport type Owner = { … };. For the shapes this tool writes, both accept the same values; the differences are covered in the TypeScript handbook). - A root that is an array, a primitive or an empty object is always a type alias, for example
export type Root = RootItem[];.
How values map to types
| JSON | TypeScript |
|---|---|
"text", 42, true | string, number, boolean |
null (and nothing else at that key) | null |
"a" in one element, null in another | string | null |
[1, "a"] | (string | number)[] |
[] | unknown[] |
{} | Record<string, unknown> |
| A non-empty object | A separate named declaration |
Union members always appear in the same order: string, number, boolean, the object type, the array type, then null. All objects found in one array are merged into a single declaration, and a key that is missing from some of them is marked optional with ?. Nested arrays merge the same way, so [[1, 2], ["a"]] becomes (string | number)[][].
Naming rule
A nested object is named after its key in PascalCase: the key is split at every character that is not an ASCII letter or digit, and each part gets a capital first letter, so user_profile, user-profile and userProfile all give UserProfile. Objects inside an array take the key’s name plus Item — items gives ItemsItem, users gives UsersItem — with one more Item per level of nesting. English words are not singularised. A name that would start with a digit gets the prefix Type.
Objects with the same keys, in the same order, with the same types share one declaration, named after the first one found. If two different shapes want the same name, the later one gets a number: Address, Address2. Declarations are listed root first, then in the order they are first reached. Keys that are not valid JavaScript identifiers, such as first-name or 2fa, are written in double quotes.
Worked example
This order record
{
"id": 1042,
"status": "shipped",
"createdAt": "2026-09-14T10:32:00Z",
"customer": { "name": "Ada Lovelace", "email": "[email protected]" },
"items": [
{ "sku": "PEN-01", "qty": 2, "price": 1.5 },
{ "sku": "PAD-07", "qty": 1, "price": 4.25, "giftWrap": true }
],
"coupon": null,
"tags": []
}
produces, with the default options:
export interface Root {
id: number;
status: string;
createdAt: string;
customer: Customer;
items: ItemsItem[];
coupon: null;
tags: unknown[];
}
export interface Customer {
name: string;
email: string;
}
export interface ItemsItem {
sku: string;
qty: number;
price: number;
giftWrap?: boolean;
}
giftWrap is optional because only the second item has it. coupon and tags show the limits of one sample; you would probably edit them to string | null and string[].
What a sample cannot tell you
- Only the values present are typed. If every
statusin your sample is"shipped", the tool cannot know the other allowed values; it writesstring, not a union of literals. Paste several records as an array for better coverage. - Numbers are just
number. TypeScript has no separate integer type, and JSON does not distinguish2from2.0(RFC 8259). Integers above 253 also lose precision when parsed. - Dates are strings. JSON has no date type, so
"2026-09-14T10:32:00Z"is typedstring. - Optional is not the same as nullable.
?means the key was absent from some objects;| nullmeans it was present with the valuenull. The real API may allow both. - Different object shapes are merged. Objects such as
{"type":"card",…}and{"type":"bank",…}in one array become one declaration, not a discriminated union. - Key order follows JavaScript. Keys keep their order of first appearance, except that integer-like keys such as
"2"come first, because that is howJSON.parseorders object properties.
Common problems
| Symptom | Cause | What to do |
|---|---|---|
| “Invalid JSON:” above the panes and “Not valid” in the output | The input does not parse; the message comes from your browser and differs between browsers | Fix the line and column shown, or check it in the JSON Formatter |
A field is typed unknown[] | Every array at that key was empty | Add a sample with at least one element, or edit the type |
A field is typed only null | The key was null everywhere in the sample | Replace with the real type, e.g. string | null |
| Too many optional fields | Objects of different kinds were merged | Split them into a discriminated union by hand |
Frequently asked questions
Is my JSON uploaded anywhere?
No. Parsing and type generation run in JavaScript in your browser, and the page does not send your input to a server. The only request involving your data is the one you trigger with the URL button, which your browser makes directly to the address you enter.
Should I choose interface or type?
Either works: for the object shapes this tool generates, both accept the same values. Interfaces can be extended and merged by redeclaring them, while type aliases can also name unions, so pick whatever your codebase or linter prefers.
Does the output check my data at runtime?
No. TypeScript types are removed at compile time, so a response that does not match goes undetected at runtime. Validate external data separately.
Why is my array of objects one interface with optional keys?
The tool merges all objects in an array into one declaration and marks every key that some elements lack as optional. That accepts the sample, but cannot tell which keys belong together.
Can it read JSON Lines or several documents at once?
No, the input must be a single JSON value. Wrap the records in square brackets and separate them with commas; the root then becomes an array and all records are merged into its item type.