json

FormatValidateConvert

In this section · 01 Turn a document into codeTypeScript

01 · Turn a document into code

TypeScript interfaces from a JSON API response

Interfaces from an API response, with the optional fields found and the nested types named for you.

Guide 2 of 8 in Generate

Input

team.json

{
  "users": [
    { "id": 1, "name": "Ada", "email": "[email protected]", "roles": ["admin"] },
    { "id": 2, "name": "Linus", "roles": [], "manager": { "id": 1, "name": "Ada" } }
  ]
}

Do

  1. Set From to JSON, and under Language choose TypeScript.
  2. In Options, under TypeScript, set Declaration to interface and Optional fields to name?: T, and tick export declarations.
  3. Paste the input into the source pane, and type Team into Root name, since a paste has no file name to take it from.

Result

export interface Manager {
  id: number;
  name: string;
}

export interface User {
  id: number;
  name: string;
  email?: string;
  roles: string[];
  manager?: Manager;
}

export interface Team {
  users: User[];
}

Try it in Generate →

Writing types by hand for an API you did not design is slow and easy to get subtly wrong. A field that is missing from one record in fifty ends up typed as required, and the bug waits for the first user who lacks it. A generator that reads real responses does the tedious part and, more usefully, notices the fields that are not always there. The example above is a small users response, and the rest of this page explains each choice the output makes. It is part of the Generate section.

Look closely at the two records before reading the output, because they do not have the same fields. Ada has an email address and no manager. Linus has a manager and no email, and his list of roles is empty. Any type written from the first record alone would be wrong about the second.

Where the name at the top comes from

The outermost interface is called Team because the document is called team.json. With Root name left empty, the page proposes a name from the file: the extension goes, the first letter is raised, and the field shows that proposal in grey. A document you paste has no file name, so it arrives as untitled.json and its root is called Root. Typing into Root name replaces the proposal, and the interfaces named after keys keep their names.

That is why the steps above ask you to type the name after pasting. The example’s Try it link does not need to: it brings the document across with its name, and the page names the root from that.

Why the output looks like this

Every record is read, not just the first

The generator walks the whole array and merges what it sees. A field present in every record is required. A field present in only some of them, like email and manager here, is marked optional with a question mark. That is the fact hand-written types most often get wrong, and it comes for free from looking at all of the data.

Nested objects get their own names

The object under manager becomes an interface called Manager, named after its key. The items of an array take the singular of the array’s key, so each element of users becomes a User.

An empty array does not throw the type off

Linus has no roles, and an empty list on its own says nothing about what it would hold. Because Ada’s record has ["admin"], the field is still typed string[]. Had every record’s list been empty, there would have been nothing to go on, and the field would come out as unknown[]: an honest answer, and a reminder to finish that type by hand.

Nulls, mixtures and awkward keys

Real responses are messier than this one, and each kind of mess has a predictable outcome. A field that is a string in one record and null in another becomes null | string, so the compiler makes you handle the gap. A field that is null in every record is typed as null and nothing else, since the sample never showed what else it could hold. An array holding numbers and strings together becomes (number | string)[] rather than pretending to be one of them. And a key that is not a valid identifier, such as first-name, is written in quotes, so the output still compiles.

When the document itself is an array, the element gets the interface and the root becomes a type alias for a list of it. Pasted, with no file name, that is RootItem and type Root = RootItem[]. Named books.json, the same document gives a Book interface and type Books = Book[].

A very large document can hold more distinct object shapes than it is sensible to name one by one. Past that point the extra shapes are folded into an open record type, and a line under the code says how many were folded, so you know the output was simplified and where to look.

TypeScript’s three options

Under TypeScript in Options, Declaration chooses between interface and type alias. The two are interchangeable for data like this; pick the one your code base already uses. Optional fields chooses how a missing field is spelled: name?: T, which lets the key be absent, or name: T | undefined, which requires the key and allows it to be undefined. Under the compiler’s exactOptionalPropertyTypes setting those two mean different things, so match the one your project assumes. export declarations puts export in front of every type, for a file other modules import from.

The output redraws the moment you change any of them. All three are remembered between visits and shared with the editor’s Generate panel, which is why the example’s steps set each one even at its default; the options card says what is remembered and what is not.

Taking the code away

Copy puts the whole output on the clipboard, and Download saves it as a .ts file named after the root. The same generator sits behind Generate in the editor band, so a response you are already reading in the editor can be typed without leaving it. For Python, Go, Rust and the rest, see The other nine languages.

The generator runs in your browser, so the responses you feed it, which often carry real customer data, are never uploaded anywhere. Open Generate with a response of your own, or Open the editor to type one you already have open.