JSON to TypeScript
Generate TypeScript interfaces from JSON data instantly
Generated TypeScript will appear here... The JSON to TypeScript converter reads a JSON sample and emits ready-to-use type declarations. Nested objects become their own named interfaces, arrays are unified into a single element type, and identical object shapes are reused instead of duplicated. Everything runs in the browser as you type, with a small debounce, so you can paste a real API response and copy the result straight into your project.
How the Inference Works
The tool parses the text with JSON.parse. If that fails, it shows the parser’s message and stops. On success it walks the value recursively:
- a string becomes
string, a number becomesnumber, a boolean becomesboolean, and bothnullandundefinedbecomeany; - an object becomes a named declaration, and its property names are converted to PascalCase for that name;
- an array inspects every element, collects the distinct element types, and joins them with
|.
Names are derived from the property path. A key named shipping_address or shipping-address both produce ShippingAddress. If a name is already taken by a different shape, the tool appends 2, then 3, and so on. Two objects with exactly the same property names, types, and optionality share one declaration, so a repeated shape is only emitted once.
Conversion is debounced by roughly 300 milliseconds, so the declarations settle shortly after you stop typing rather than on every keystroke. The root name goes through the same PascalCase conversion as every other identifier: a root typed as order response becomes OrderResponse, and that name seeds the top-level declaration plus the derived names of nested types.
Worked Example: An API Response
Given this input:
{
"id": 1,
"name": "Ada",
"tags": ["math", "code"],
"address": { "city": "London", "zip": "E1" }
}
the output is:
export interface Root {
id: number;
name: string;
tags: string[];
address: Address;
}
export interface Address {
city: string;
zip: string;
}
The root object always appears first, followed by the nested declarations in the order they were discovered. tags is a homogeneous array, so it becomes string[]; address becomes a reference to its own interface.
Handling Arrays and Optional Keys
An array of objects is merged into one interface. Each key appears once, and any key that is missing from at least one element is marked optional. For example:
{ "users": [
{ "id": 1, "email": "a@example.com" },
{ "id": 2, "name": "Bob" }
] }
produces:
export interface Root {
users: Users[];
}
export interface Users {
id: number;
email?: string;
name?: string;
}
The element interface is named Users because it is derived from the users key. If elements have conflicting types for the same key, those types are unioned, so a field that is a number in one object and a string in another becomes number | string. A mixed primitive array such as [1, "two"] becomes (number | string)[], while [1, 2, 3] stays number[].
Property names that are not valid JavaScript identifiers are quoted with JSON.stringify, so a key like "first-name" is emitted as "first-name": string;.
interface vs type
The declaration toggle changes the syntax, not the inferred structure. In interface mode the tool writes export interface Name { ... }; in type mode it writes export type Name = { ... };. Interfaces support declaration merging and are the conventional choice for object shapes; type aliases are more uniform when you later want to combine them with unions. The export checkbox simply adds or removes the export prefix, which is useful when pasting into a file that already imports these names.
One consequence of the toggle is worth knowing: a root array always produces a type alias regardless of the setting, because an array is not an object shape. Even in interface mode you will see a single type Root = RootItem[]; line, paired with an interface for the element shape.
Accuracy Limits
nullandundefinedcollapse toany, so nullable fields lose their intent.- An empty array becomes
any[], which is permissive rather than precise. - A scalar root, such as
"hello"or[1, 2, 3], is emitted as a type alias, not an interface. - Only arrays reveal optionality. A field absent from the sample is not marked optional.
- Integers and decimals both become
number; there is nobigint,Date, or decimal type. - String values do not become literal unions, so
"active"is typed asstring. - The output is compile-time only and performs no runtime validation.
Practical Tips
Paste the most complete response you have, ideally one that exercises optional fields and every array variant. Run the JSON through a formatter first if it arrived minified, since the converter shows the parser error verbatim. Keep the root name meaningful (UserResponse, Order), because every nested interface is named from it. When the inferred types are too loose, hand-edit specific fields to add literal unions or nullability; the generated code is a starting point, not a contract.
Watch for accidental shape sharing. If two unrelated nested objects happen to have the same field names, types, and optionality, they are merged into one declaration. That is usually what you want, but it means the shared type must stay valid for both call sites; rename a key or split the sample if the two shapes are meant to evolve independently.
Related Tools
- JSON Formatter — format, validate, and beautify JSON before converting it.
- JSON Schema Generator — infer a JSON Schema from your data for runtime validation.
- YAML Converter — convert YAML configs to JSON first, then generate types.
Frequently Asked Questions
Why did my JSON null become any instead of null?
The tool maps both null and undefined to any, because a single sample cannot tell whether a field is always null or merely null in this one response. If you want explicit null handling, edit the generated declaration to use `string | null` or a similar union the tool cannot infer.
How does the generator decide that a property is optional?
Only arrays teach it optionality. When an array of objects is merged into one interface, any key that is missing from at least one element is marked with a question mark. In a plain object, every key is treated as required because there is no counter-example in the input.
What does it do with an empty array?
An empty array has no elements to inspect, so the tool emits any[]. That is a signal to fill the type in by hand, since a later non-empty response would otherwise be silently accepted. Paste a representative sample that includes at least one element.
Will it infer string literal unions such as "active" | "paused"?
No. A JSON string is always inferred as the broad string type, and a number as number. Literal unions, enums, branded ids, and date strings all require a manual edit, because inference from one value cannot prove that other values are impossible.
Why is my root array declared as type Root = Items[] instead of an interface?
An interface describes an object shape, but the root here is an array, so the tool emits a type alias. It also names the element interface after the root with an Item suffix, for example RootItem, so the alias and the element type never collide.
Can I use the output directly with Zod or io-ts for runtime validation?
No. The generator produces plain TypeScript declarations, which exist only at compile time and are erased from the running JavaScript. Pair the output with a runtime schema library if you need to reject malformed data at run time.
Reviewed by the HandyTools Hub team · Last updated