What JSON to TypeScript does
This generator reads a JSON sample and writes one exported TypeScript interface per distinct object shape. Strings, numbers and booleans map to string, number and boolean; a nested object becomes its own interface named after its key in PascalCase; an array becomes the element type followed by []. The default sample, an object with id, name, active, tags and a nested profile, produces a Profile interface and a Root interface whose profile field is typed as Profile. Types come from the single sample you paste, so the output is a first draft: a field that is null in the sample, or absent from it, has to be corrected by hand.
JSON to TypeScript type mapping used by this generator
| JSON value | Generated TypeScript | Notes |
|---|---|---|
| "Ada Lovelace" | string | Dates and UUIDs are strings too: "2026-10-08T09:30:00Z" gives string |
| 42, 9.99, 1e-3 | number | No distinction between integers and decimals |
| true or false | boolean | Never narrowed to the literal true or false |
| null | null | Written as key?: null when Mark null values optional is ticked |
| {"city":"London"} | A named interface | Named after the key in PascalCase |
| {} (empty object) | Interface with [key: string]: unknown; | An index signature, since there are no keys to list |
| ["a","b"] | string[] | One element type gives a plain array type |
| [1,"two",true,null] | (number | string | boolean | null)[] | Union of every element type seen, in order of first appearance |
| [[1,2],[3,4]] | number[][] | Nested arrays nest the brackets |
| [] (empty array) | unknown[] | Nothing to inspect; replace with the real element type |
| [{"id":1},{"id":2}] | Item[] style: an interface plus [] | Interface name is the key made singular |
Worked examples: JSON in, interfaces out
Results are the generator's exact output, shown on one line with interfaces separated by a slash.
The default sample
{"id":42,"name":"Ada Lovelace","active":true,"tags":["math","computing"],"profile":{"city":"London","followers":1024}}
export interface Profile { city: string; followers: number; } / export interface Root { id: number; name: string; active: boolean; tags: string[]; profile: Profile; }
Summary: Interfaces 2.
A paginated API response
{"data":[{"id":1,"title":"Post","author":{"id":9,"name":"Ada"},"tags":["a"]}],"meta":{"page":1,"total":40},"error":null} with Root interface name PostsResponse
export interface Author { id: number; name: string; } / export interface Data { id: number; title: string; author: Author; tags: string[]; } / export interface Meta { page: number; total: number; } / export interface PostsResponse { data: Data[]; meta: Meta; error?: null; }
Four interfaces. Rename Data to Post and replace error?: null with the real error type.
Two objects with the same shape
{"billing":{"street":"1 Main St","city":"Leeds"},"shipping":{"street":"2 Side St","city":"York"}} with Root interface name Order
export interface Billing { street: string; city: string; } / export interface Order { billing: Billing; shipping: Billing; }
One interface is reused and takes its name from the first key that had that shape. Renaming it Address reads better.
Array elements with different fields
{"users":[{"id":1,"name":"Ada"},{"id":2,"name":"Grace","email":"g@example.com"}]} with Root interface name UserList
export interface User { id: number; name: string; } / export interface User2 { id: number; name: string; email: string; } / export interface UserList { users: (User | User2)[]; }
Shapes are not merged. Collapse these by hand into one User with email?: string.
Keys that are not valid identifiers
{"first-name":"Ada","2fa":true,"user id":7,"$ref":"x","_private":1} with Root interface name api response
export interface ApiResponse { "first-name": string; "2fa": boolean; "user id": number; $ref: string; _private: number; }
Keys with hyphens, spaces or a leading digit are quoted; $ and _ are legal and left bare.
A root that is not an object
[1,2,3] with Root interface name Scores
export type Scores = number[];
Summary: Interfaces 0.
What one JSON sample cannot tell the generator
Every type on this page is inferred from the values in the sample you paste and from nothing else. That has three consequences worth knowing before you commit the output.
A field that is null in the sample is typed null. The generator has no way to know it is a string on other responses, so nickname comes out as nickname?: null and you write string | null yourself. Pasting a sample where the field has a value gives the right base type, and you then add | null.
A field that is absent from the sample does not appear at all, and a field that is present is never marked optional unless its value is null. The question mark this tool writes means the value was null here, not that the key is sometimes missing.
Objects inside an array are compared, not merged. If every element has the same keys, in the same order, with the same types you get one interface. If one element has an extra key, lists its keys in a different order, or has a null where another has a string, you get two interfaces and a union such as (Row | Row2)[]. The fix is to merge them by hand into one interface with optional or nullable fields, or to paste a sample whose elements are all complete.
How interfaces are named
| Situation | Example key or name | Interface name |
|---|---|---|
| Root object | Root interface name left as Root | Root |
| Root name with spaces or punctuation | api response | ApiResponse |
| Root name starting with a digit | 2fa | Item2fa |
| Root name left blank | (empty) | Root |
| Nested object | profile | Profile |
| Objects in an array, plural ending in s | items, users | Item, User |
| Plural ending in ies | categories | Category |
| Plural ending in xes, ses, ches, shes or zes | boxes, addresses | Box, Address |
| Words the suffix rule gets wrong | status, cases | Statu, Cas (rename them) |
| A word with no plural form | data | Data |
| Name already taken by a different shape | a second profile object with other fields | Profile2 |
| Nested key that matches the root name | root inside Root | The nested object takes Root and the root becomes Root2 |
Names are derived mechanically from keys. The singular rule is a simple suffix rule, so check names that come from array keys.
Checklist before you use the generated types
- Search the output for null and replace each one with the real type, such as string | null.
- Search for unknown and fill in element types for arrays that were empty in the sample.
- Add ? to any field the API omits in some responses.
- Merge numbered duplicates such as User and User2 into one interface.
- Narrow fixed sets of strings to literal unions, for example status: "draft" | "published".
- Rename interfaces that inherited an unhelpful key name, such as Data or Statu.
- Remember that interfaces are erased at compile time. They describe the response but do not validate it; use a runtime schema library if you need to reject bad data.
Using JSON to TypeScript
- 1
Paste a representative response into JSON. It has to be strict JSON: double-quoted keys, no trailing commas, no comments.
- 2
Set Root interface name to something meaningful such as OrderResponse. It is converted to PascalCase, so api response becomes ApiResponse.
- 3
Leave Mark null values optional ticked to write a null field as name?: null, or untick it to write name: null.
- 4
Copy the TypeScript or download it as types.ts. The Summary shows how many interfaces were generated.
- 5
Replace every null and unknown[] with the real type, and add ? to fields your API can leave out.