JSON to TypeScript Interfaces: What Gets Inferred, What to Fix
Paste a JSON sample and get TypeScript interfaces. What the generator infers, what it misses (optional fields, null, unions, dates) and how to fix each by hand.
To turn JSON into TypeScript types, paste one sample object (or an array of objects) into the free JSON to TypeScript tool, type a root name such as User, and copy the exported interfaces. It makes one interface for every nested object and types each array from its first element. It runs in your browser, so the sample never leaves your computer.
The catch is that it sees only the sample you paste. It can't know which fields are sometimes missing, which values can be null, or that a string holds a date. Those are fixes you make by hand, and this guide shows each one, using output from the tool itself.
What the generator produces
Here is a typical API response, entered with the root name User:
{"id": 1042, "name": "Asha Rao", "is_active": true, "created_at": "2026-10-21T09:30:00Z", "manager": null, "tags": ["admin", "beta"], "scores": [], "address": {"city": "Pune", "pin": "411001"}, "orders": [{"order_id": "A-77", "amount": 1499.5, "items": [{"sku": "B12", "qty": 2}]}], "first-name": "Asha"}
The tool returned four interfaces (shown here one per line):
export interface User { id: number; name: string; is_active: boolean; created_at: string; manager: any; tags: string[]; scores: any[]; address: Address; orders: Orders[]; "first-name": string; }export interface Orders { order_id: string; amount: number; items: Items[]; }export interface Items { sku: string; qty: number; }export interface Address { city: string; pin: string; }
The root comes first, then the nested interfaces. Every field is required, and keys that aren't valid identifiers, like first-name, are quoted. This table shows how each kind of value is typed.
| Value in your JSON | Generated type | What to check |
|---|---|---|
"Pune" | string | Dates, IDs and fixed codes also become plain string |
1042, 1499.5 | number | Fine; TypeScript has one number type |
true | boolean | Nothing |
null | any | Replace with the real type plus | null |
[] | any[] | Add the element type |
[1, "two", null] | number[] | Only the first element is read |
[[1, 2], [3, 4]] | number[][] | Nothing |
| A nested object | Its own interface, named after the key | Plural or odd names |
How the tool names interfaces
- Root: whatever you type, with spaces and symbols removed, and not capitalised. "user" gives
interface userand "api response" givesapiresponse, so type it in PascalCase. - Nested objects: the key with its first letter capitalised.
addressbecomesAddress, butordersbecomesOrdersfor a single order,line_itemsbecomesLine_itemsanduser profilebecomesUserprofile. Rename these toOrder,LineItemandUserProfile. - Same key in two places: if
billing.addressandshipping.addressboth exist, you get oneAddressinterface built from the first. Alandmarkfield that appears only in the shipping address is lost. - A key that matches the root name: with the default root
Root, a key calledrootis typed asroot: Root, which points back at itself. Choose a different root name. - A key starting with a digit:
{"2fa": {...}}producesexport interface 2fa, which doesn't compile (TypeScript 7.0 reports error TS1351). Rename it, for example toTwoFactor.
What you must fix by hand
Optional fields
The tool never writes ?. For an array it reads only the first object, so [{"id": 1, "name": "A"}, {"id": 2, "name": "B", "phone": "98"}] gives Customer { id: number; name: string; } and phone disappears. Your code then fails to compile at list[0].phone with "Property 'phone' does not exist on type 'Customer'". Add phone?: string;. Before generating, put the object with the most fields first in the array.
null and any
manager: any turns checking off. With strict on, user.manager.name still compiles, then crashes at runtime when the manager is null. Find a sample where the field is filled, generate its interface, and write manager: Manager | null;.
Unions and fixed values
A mixed array such as [1, "two", null] needs (number | string | null)[]. A field like status comes out as string; if the API documents its values, a union such as "paid" | "pending" | "refunded" catches typos at compile time.
Dates arrive as strings
created_at: string is correct. JSON has no date type, and JSON.parse gives you the text. Changing the type to Date without converting the value is a lie the compiler can't catch. Keep string with a comment such as "ISO 8601 date-time", and convert where you need it with new Date(user.created_at).
Very large IDs
JavaScript numbers are exact only up to 9,007,199,254,740,991. JSON.parse('{"id": 9007199254740993}') returns 9007199254740992, so a 64-bit database ID can silently change. If an API sends such IDs, ask for them as strings and type them as string.
Interface or type?
The tool writes export interface, and changing it to export type User = { ... } works the same for object shapes. The TypeScript handbook says almost every interface feature is also available in type; the key difference is that an interface can be reopened to add fields, and a type can't. Declaring interface Settings twice merges the two, while declaring type A twice gives "Duplicate identifier 'A'". The handbook's rule of thumb is to use interface until you need a feature of type.
You need type for unions (type Status = "paid" | "pending"), tuples and mapped types. A common split is interfaces for API objects and types for unions and helpers. Partial<User> makes every field optional, which suits update payloads.
Types vanish at runtime: validate API data
TypeScript types are removed when your code is compiled. If the server sends "id": "1042" as a string, nothing stops it. For data from outside your code (APIs, uploaded files, localStorage), check it at runtime with a schema library such as Zod, Valibot or ArkType.
With Zod 4.6, the schema also gives you the type, so there is only one definition to maintain:
const User = z.object({ id: z.number().int(), email: z.email(), created_at: z.iso.datetime(), manager: Manager.nullable(), phone: z.string().optional() });type User = z.infer<typeof User>;const user = User.parse(await res.json());
When we fed it "id": "1042" and "created_at": "21/10/2026", safeParse reported "id: Invalid input: expected number, received string" and "created_at: Invalid ISO datetime". If you want a real Date, z.coerce.date() converts the string for you. The generated interfaces are a good starting point for writing such a schema by hand.
A workflow that holds up
- Collect two or three real responses, including one where optional fields are filled and one where they are
null. - Check the sample with the JSON validator; if it fails, the guide to common JSON errors explains the message.
- Put the fullest object first, generate, and rename the interfaces.
- Replace every
any, add?and| null, add unions, and comment date strings. - Validate at the boundary where data enters your app.
- When the API changes, regenerate from a fresh sample and compare it with your edited file rather than overwriting it.
If your data starts in a spreadsheet, convert it first with the Excel to JSON converter; see how to convert Excel to JSON for how it handles headers and dates. For key naming such as is_active versus isActive, see camelCase vs snake_case.
Frequently asked questions
Can it generate types from an array of objects?
Yes, but it reads only the first object. Fields that appear only in later objects are left out, so put the fullest object first and mark the rest optional by hand.
Why is a field typed as any?
The value in your sample was null, or an empty array (any[]). Use a sample where the field has a real value, or write the type yourself.
Should date fields be typed as Date?
Only after you convert them. JSON delivers dates as strings, so the raw response type should say string.
Is my JSON uploaded anywhere?
No. The JSON to TypeScript tool runs entirely in your browser, which makes it safe for responses containing real customer data.
Interface or type for API responses?
Either works for object shapes. Use interface by default and type when you need unions, tuples or mapped types.
Sources: TypeScript Handbook, "Everyday Types" (type aliases and interfaces); Zod 4 documentation; output from the site's JSON to TypeScript tool, checked with TypeScript 7.0.2 and Zod 4.6.7, October 2026.
- #json to typescript
- #typescript interface from json
- #type vs interface
- #zod
- #api response types