JSON to TypeScript
Paste JSON and get clean TypeScript interfaces: nested objects, arrays, deduplicated shapes and a custom root name. Runs entirely in your browser.
Why generate types from JSON
When an API returns a payload, someone has to describe it in TypeScript by hand — field by field, level by level. It is slow, and the second version of the endpoint makes it stale. This tool reads the JSON once and writes the interfaces for you: every nested object becomes an interface, arrays become typed lists, and identical shapes are deduplicated automatically.
What it does
- Interfaces for every nested object, with correct field names — quoted where necessary.
- Typed arrays, including unions when an array mixes shapes.
- Deduplication: objects with the same shape reuse one interface, and a custom root name.
How to use
- Paste your JSON (an API response, a config, anything valid).
- Set the root interface name if you dislike the default.
- Copy the generated TypeScript into your project.
How it works
The generator walks the parsed JSON recursively, infers a type per value, names object interfaces after their key in PascalCase and remembers each shape’s signature so identical objects reuse a single interface. Everything runs locally in your browser.
Why describe your data from an API response
This is a routine story. The frontend received a response from the backend, opened it in the console, copied it and started rewriting it as an interface. An hour later it turns out a field is named differently than expected and a nested object is actually an array of objects. Another hour later the staging environment returns a slightly different shape and the description has to be fixed again. A type generator does that work in a second: paste the JSON, get the interfaces, drop them into the project.
The second scenario, no less common, is working with configuration files and with saved responses. A config, a database dump, a payload captured from a third-party service, a test fixture. All of it is JSON, and all of it is worth typing properly in a TypeScript project so the editor can autocomplete the fields. It is even better when the description sits next to the data and changes together with it.
A third scenario deserves a word of its own, and it is about testing an assumption. You need to confirm that the response shape makes sense, that the nested structures are consistent and that objects with the same shape can be described by one interface instead of ten near-copies. Here the generator doubles as an analysis tool: look at the tree of interfaces that comes out and the structure of the data becomes obvious. To compare two versions of a response there is the JSON diff tool, and to check a schema the JSON Schema validator.
- Describing an API response that a neighbouring service just changed.
- Typing a configuration file that lives in the repository.
- Creating an interface for a test fixture or a mocked response.
- Untangling a nested response that is hard to read in a console.
- Checking whether objects in different branches of the response share a shape.
- Getting a quick draft type for an experiment in the editor.
- Refreshing a description after a partner changed the response format.
- Preparing a documentation sample where a readable data type is needed.
Mistakes that leave the types slightly wrong
The first mistake jumps out at you, and it is about interface names. The tool derives a name from the key and applies the usual English rule for dropping a plural, but that rule only works for English. The key `address` becomes `Addres`, `status` becomes `Statu`, `series` becomes `Serie`, because a trailing `s` is taken as a plural marker. With `categories` you do get the correct `Category`, but with Russian or otherwise non-English keys the result is nowhere near what you want. The names are worth fixing by hand.
The second mistake is about the types coming out mandatory. Every field is marked as present, even though in the real response some of them may be absent. Where a field turns up only sometimes, a question mark has to be added, or the code will assume a defined value and fall over on `undefined` during parsing. The third mistake concerns union types: when an array mixes different shapes, the generator honestly produces a union, but narrowing that union in real code is manual work, and that is a normal part of the job.
The fourth mistake is about fields holding `null`. The tool turns them into the type `null`, which is correct, but in a real response the field may be either `null` or an ordinary value, and then you need something like `string | null`. Empty arrays work the same way: they come out as `unknown[]`, because there is nothing to infer from, and that is more honest than guessing. The fifth mistake is worth remembering, and it is about dates. The string `2024-03-10` stays a `string`, and if your project uses `Date`, the type has to be adjusted by hand. Before adjusting, it helps to look at what actually arrived through the JSON formatter.
- Interface names formed badly in the singular: `address` yields `Addres`, `status` yields `Statu`.
- Every field marked as required, when some of them are sometimes absent.
- A `null` field typed as `null` only, with no variant for an ordinary value.
- An empty array left as `unknown[]` and never refined.
- Dates left as strings even though the project uses `Date`.
- Expecting the generator to emit a `type` alias or add `readonly`.
- Trying to get an enum out of a field with string values. You get a plain `string`.
- Expecting nested objects with the same fields to get separate descriptions. The opposite happens, they are merged.
How the JSON is walked and what it can do
The work starts with `JSON.parse`. If the text is invalid, the parser returns an error with a position and generation does not start. The parsed structure is then walked recursively. A type is determined for every value: `null` becomes `null`, strings, numbers and booleans get their matching primitives, and an array becomes a list. When the elements of an array are all the same type you get `T[]`, and when they differ you get a union in parentheses, something like `(string | number)[]`.
When the walk reaches an object, a separate interface is created for it. The name comes from the key the object was found under and is converted to PascalCase, so `user_profile` becomes `UserProfile`. Keys that are not valid TypeScript identifiers are wrapped in quotes, exactly as the language requires. To stop objects of the same shape from multiplying, a signature is computed for every set of fields: the fields are sorted and joined into a string, and identical signatures resolve to one shared interface.
One detail about the root element is worth knowing. If the JSON holds an object, an interface is created with the name you supplied, `Root` by default. If the root is an array, the description takes the form `export interface Root { value: T[]; }`, so the array is wrapped in a field. If the root is a primitive such as a number, you get an interface with a single `value` field. Nested interfaces are emitted before the root one, and all of them carry the `export` keyword so they can be imported individually.
How JSON values map to types
| Value in JSON | Type in TypeScript | Comment |
|---|---|---|
| "text" | string | The type follows the value |
| 42 | number | Integers and decimals share one type |
| true | boolean | Booleans are kept distinct |
| null | null | No union with other types is created |
| [1, 2, 3] | number[] | Uniform elements give a plain list |
| ["a", 1] | A union of string and number | A mixed array gives a union of the types inside |
| [] | unknown[] | There is nothing to infer from |
| {...} | A separate interface | Named after the key |
| "key-with-dash" | "key-with-dash": string | An invalid identifier is quoted |
Objects with the same set of fields share one interface: the shape signature is computed from the sorted list of fields and their types.
Generating types and the other ways to describe data
Generating from JSON is convenient because it starts from something real and plausible. There are alternatives, though, and each has its own strength. Writing the types by hand works, and it is arguably better, because you think about optional fields, date formats and which values are even allowed at the moment you write them. The cost is time, and for hand-written descriptions time is worth more than seconds.
JSON Schema describes data formally, validates it and works across languages, but it needs a separate file and its own structure. Generating types from a schema makes sense when a schema already exists. The third and strictest option is runtime validation with Zod or a similar library, where a single description drives both the types and the checks. The API response is then validated before it reaches the code, but that means a real dependency rather than a page in a browser.
Converting to other formats deserves a mention too, because JSON often arrives in a shape you would rather not keep. In YAML it becomes easier to read, in CSV a table is easier to work with, and in JSON Schema it turns into a formal description you can validate against. For reaching into JSON by path there is the JSON path finder.
Comparing ways to describe data
| Approach | Strength | What you pay for it |
|---|---|---|
| Generating from JSON | Fast, and always grounded in reality | Mandatory fields, awkward names, no dates |
| Writing it by hand | Full control over the types | Time spent on every single field |
| JSON Schema | Formal and validatable | A separate file and schema syntax |
| Schema as the source | One source of truth | The schema also has to be stored and maintained |
| Runtime validation | Errors surface immediately, before use | An external library and its setup |
| Plain TypeScript | Nothing extra | A weak guarantee, since the values arrive from outside |
| Editing the generated output | Targeted fixes | Every change has to be repeated by hand |
Before describing a type, look at the response itself: the JSON formatter shows it readably, and the schema validator checks it against a specification.
Habits that make generated types usable
A generated type is a draft, not a finished result. A few habits turn it into something you can actually commit to a project.
- Give the root interface a meaningful name such as `User` instead of leaving the default.
- Walk through the interface names and fix the awkward ones, such as `Addres`.
- Mark the fields that are sometimes absent with a question mark.
- Add `| null` to fields that arrive empty, or the type will be too narrow.
- Change date types from `string` to `Date` if the project uses the Date object.
- Check whether objects with different meanings were merged into one interface. The same shape does not imply the same meaning.
- Keep the generated type next to the test fixture so it does not go quietly out of date.
Frequently asked questions
How are field names handled?
What happens with empty arrays or nulls?
Related articles
Related tools
JSON to CSV Converter
Convert JSON array data to CSV format with auto-detected columns
JSON to YAML Converter
Convert JSON data to YAML format with proper indentation
YAML to JSON Converter
Convert YAML data to JSON format with support for nested structures
Markdown to HTML Converter
Convert Markdown to HTML with live preview support
HTML to Markdown Converter
Convert HTML to Markdown format with proper element handling
SQL Formatter & Beautifier
Format and beautify SQL queries with keyword casing and indentation
JS / CSS / HTML Minifier
Minify JavaScript, CSS, and HTML to reduce file size with size reduction stats
JSON Path Finder
Find paths to values and keys in JSON objects with powerful search