JSON to TypeScript

Generate TypeScript interfaces from JSON

About JSON to TypeScript

JSON to TypeScript reads a JSON sample and writes the TypeScript interfaces that describe it, using the quicktype-core engine in your browser. Every nested object becomes its own exported interface, and every element of an array is examined: a key that some elements lack gets a question mark, and a value that is sometimes null is typed with null in a union. Keys that are not valid identifiers, such as first-name, are written in quotes. It generates types only: no classes, no runtime checks, and no enums, dates, or UUID types guessed from string values.

Example

Two user records from an API: lastLogin is null in the second, address exists only in the first, tags is empty in the second, and manager-id is not a valid identifier. The output below is exactly what "Convert" writes.

JSON input

[{"id": 101, "email": "ada@example.com", "role": "admin",
  "lastLogin": "2026-10-01T08:30:00Z",
  "address": {"city": "London", "zip": "N1 9GU"},
  "tags": ["beta"], "manager-id": null},
 {"id": 102, "email": "lin@example.com", "role": "editor",
  "lastLogin": null, "tags": [], "manager-id": 101}]

TypeScript output

export interface Root {
    id:           number;
    email:        string;
    role:         string;
    lastLogin:    null | string;
    address?:     Address;
    tags:         string[];
    "manager-id": number | null;
}

export interface Address {
    city: string;
    zip:  string;
}

How to generate TypeScript interfaces from JSON

  1. 1Paste a JSON object, or an array of several representative objects, into "JSON input".
  2. 2Click "Convert"; the engine loads on the first click, then the interfaces fill "TypeScript output".
  3. 3Check the optional keys and the null unions against the data your code will really receive.
  4. 4Click "Copy", or "Download" to save the code as "types.ts".

Formats and limits

Input
JSON text: an object, or an array of objects whose elements are merged; an array of plain values gives a message instead of code
Output
Exported TypeScript interfaces, one per nested object, saved as types.ts
Optional keys
A question mark when a key is missing from some array elements; null in a union when a value is null
Not generated
Classes, runtime validation, enums, date, or UUID types (strings stay string)

Common uses

Typing a REST API response
A front-end developer copies a GET /users response from the browser's Network tab, pastes the whole array, and gets a Root interface describing one user, plus one interface per nested object, to type the result of the fetch call.
Describing a settings file
A JSON file of feature flags or app settings becomes an interface, so the editor autocompletes its keys and reports a misspelled one in the code that reads them.
Finding the fields a feed leaves out
Pasting 50 records from a webhook log or a data export shows, through question marks and null unions, which keys some records omit and which are sometimes null, before the code assumes they are always there.

Which tool to use

  • JSON Formatter: when "Convert" answers with an Invalid JSON message, it shows the faulty line with a caret under the problem, so you can fix the sample and paste it back here.
  • JSON to Go Struct: the same quicktype engine and the same merging of array elements, written as Go structs with json tags and int64 or float64 fields instead of TypeScript interfaces.
  • TypeScript to JavaScript: the opposite job: it strips type annotations and interfaces from TypeScript source to leave plain JavaScript, and it does not read JSON.

Where your JSON is processed

The interfaces are generated in your browser: the page fetches the quicktype engine on your first click, and your JSON is not uploaded, nor is the TypeScript it produces. If an error message appears, a shortened copy of that message, with quoted text, long numbers, and addresses removed, is sent to our error log together with the tool's name and your browser's name and version, so the tool can be fixed.

Frequently asked questions

Does it detect optional fields?

Yes, when you paste an array. All elements are merged: a key missing from some of them is written with a question mark, and a key that is null in some of them gets null in its type, for example null | string. With a single object, every key it contains is required.

How are nested objects and arrays typed?

Each nested object becomes a separate exported interface named after its key, such as Team for a team key, and an array of objects becomes an array of that interface. Arrays of strings or numbers become string[] or number[]. A tree that repeats its own shape, such as {"name": "a", "children": [{"name": "b", "children": []}]}, gives children: Root[] instead of an endless chain of interfaces.

What happens to keys with hyphens or spaces?

They keep their exact spelling as quoted property names, which is valid TypeScript, so the interface matches the JSON without any renaming. Keys that are valid identifiers, like nickname, are written without quotes.

Can I save the result as a .ts file?

Yes. After a conversion, a row named types.ts appears under the output with a "Download" button; on iPhone and iPad a "Save or share" button is added next to it. "Copy" puts the same code on the clipboard.

What type does a field get when its values mix numbers and strings?

A union of every type found. [{"a": 1}, {"a": "x"}] gives a: number | string, and an array holding 1, "a", and true is typed Array<boolean | number | string>.

What does an empty array become?

unknown[], because the sample shows nothing about its elements. Paste records where that array holds at least one element, and the merged type becomes string[], number[], or an interface array.

Can I set the root name or get type aliases instead of interfaces?

No. The top-level interface is always called Root and every shape is written as an exported interface. Rename Root in your editor, and change interface to type yourself if your codebase prefers aliases.

What if two nested objects share a key name but not the same fields?

Each shape gets its own interface, prefixed with its parent: an address under user and another under company become UserAddress and CompanyAddress, so neither one overwrites the other.

Are long numeric IDs safe to type as number?

Not always. JavaScript holds whole numbers exactly only up to 9,007,199,254,740,991, and every JSON number is typed number here. When the JSON holds a whole number beyond that, a comment at the top of the output says how many: JSON.parse rounds them, so keep such IDs as strings in the API or read them as a bigint.

Why do two different keys share one interface with an odd name?

Objects with exactly the same fields and types are merged into one interface, named from the keys that hold them. {"home": {"city": "x", "zip": "1"}, "work": {"city": "y", "zip": "2"}} gives home: Home and work: Home; billing and shipping with the same fields share an interface named Ing, the ending the two names have in common. The shape is right: rename the interface in your editor.

When does an object become { [key: string]: T } instead of an interface?

When its keys look like data rather than field names and all its values have one type. {"byId": {"1": {...}, "2": {...}}} gives byId: { [key: string]: ByID }, and exchange rates keyed USD, EUR, GBP, and JPY give { [key: string]: number }. With only USD, EUR, and GBP, or with word keys such as red, green, and blue, each key stays a named field. This is a heuristic of the engine, so check objects keyed by IDs or codes.

Why does an array of strings or numbers give no interface?

A top-level array of plain values, such as ["a", "b"], [1, 2, 3], [], or [[1]], has no object to describe, so the engine writes no code and the page shows a message instead. Wrap the array in an object: {"items": ["a", "b"]} gives items: string[].

Why is an ISO date typed as string instead of Date?

Because JSON.parse returns "2026-10-01T08:30:00Z" as a string, not a Date. An interface that says Date would let code call getTime() on a value that is still text at runtime. The same goes for UUIDs and numbers written in quotes such as "42": they stay typed as string until your code converts them.

Does it check that the data really matches the interfaces?

No. TypeScript interfaces are erased when the code is compiled, so they describe the data you expect without checking what arrives. The output contains no validation code and no Zod schema; use it as the shape to follow when you write one.

Tips and tricks

  • ✓Rename the Root interface after pasting it into your project; the name comes from the tool, not from your data.
  • ✓If the result is an Invalid JSON message, open the same text in JSON Formatter, which points to the line and column of the mistake.