JSON to TypeScript Converter

Turn a real API response into named interfaces — optional fields and all.

313 characters
interface ApiUser {
  id: number;
  name: string;
  email: string;
  active: boolean;
  address: Address;
  tags: string[];
  orders: Order[];
  lastLogin: null;
}

interface Order {
  id: number;
  total: number;
  status: string;
  trackingCode?: string;
}

interface Address {
  city: string;
  zip: string;
}
Written and maintained by Pura IALast reviewed

Pasting an API response and getting back typed interfaces is a genuinely different job from formatting JSON, so it gets its own generator rather than being one more output style bolted onto the pretty-printer. It walks the parsed value once: every object becomes a named interface, arrays of objects are merged into a single interface, and a field that is missing from some (but not all) items in an array becomes optional rather than producing a separate type per item.

The sample below is loaded in the editor above with JSON → TypeScript selected. It is a realistic shape — a user record with a nested address, an array of string tags, and an array of orders where only one order has a trackingCode — chosen because it exercises the three cases that matter: nesting, arrays, and inconsistent optional fields.

Before and after

This is what is loaded in the editor above.

Pasted
{
  "id": 4821,
  "name": "Ana Torres",
  "email": "[email protected]",
  "active": true,
  "address": {
    "city": "São Paulo",
    "zip": "01310-100"
  },
  "tags": [
    "vip",
    "wholesale"
  ],
  "orders": [
    {
      "id": 1,
      "total": 129.9,
      "status": "shipped"
    },
    {
      "id": 2,
      "total": 44,
      "status": "pending",
      "trackingCode": "BR928374"
    }
  ],
  "lastLogin": null
}
Result
interface ApiUser {
  id: number;
  name: string;
  email: string;
  active: boolean;
  address: Address;
  tags: string[];
  orders: Order[];
  lastLogin: null;
}

interface Order {
  id: number;
  total: number;
  status: string;
  trackingCode?: string;
}

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

423 bytes → 313 bytes — a 26% reduction on this sample.

How it works

How nesting becomes named interfaces

Every object field gets its own interface, named after the field (address becomes Address, orders' element becomes Order). This is deliberate: an inline nested type is harder to reuse and harder to read in an editor's hover tooltip than a named one. Two fields with the exact same set of keys and types — a billing address and a shipping address, for instance — are recognised as the same shape and share one interface instead of generating a duplicate.

Arrays of objects are merged, not enumerated

orders is an array where the first item has no trackingCode and the second does. Rather than generating Order and Order2, the generator looks at every item in the array, collects the union of every key that appears in any of them, and marks a key optional if it is missing from at least one item — which is exactly what trackingCode?: string above expresses. This mirrors how you would type it by hand after actually reading a few sample responses.

Mixed-type arrays become a union

An array whose elements are not all the same type — [1, "two", 3] — produces (number | string)[] rather than picking one type and hiding the mismatch, or refusing to generate anything. An empty array has nothing to infer from and is typed unknown[]; narrow it by hand once you know what the array is supposed to hold.

interface Root {
  values: (number | string)[];
}

What it deliberately does not infer

Every string becomes string and every number becomes number — there is no attempt to detect that a field always looks like a date, an email, or one of three fixed values and narrow it to a literal union or a branded type. Reliable narrowing needs either a larger sample than one response or domain knowledge this tool does not have; guessing wrong would be worse than leaving the field as string.

Known limitations

  • One example response is one sample, not a schema. A field that happens to be null or a whole number in your one paste, but is sometimes a string or a decimal in other responses, will be typed too narrowly. Test the generated interface against a few different real responses, not just one.
  • Generated interface and property names come from your JSON keys and are not deduplicated beyond identical field shapes — two differently-shaped objects that both come from a field called "data" will both be named Data and Data2, which reads as "the second one" rather than anything descriptive. Rename them once you know what they represent.

JSON to TypeScript FAQ

Does it handle deeply nested JSON?

Yes — nesting has no depth limit. Every nested object becomes its own named interface, however many levels deep it appears.

What happens with an array that mixes objects and primitives?

The object items are merged into one interface as usual, the primitive items contribute their own types, and the array's element type is the union of both — for example (Order | string)[].

Can I set my own root interface name?

The generated root interface is named Root by default when you edit the JSON in the tool. This page names it after what the sample represents (ApiUser) purely for readability in the example.

Is my JSON sent to a server to generate the types?

No. The generator runs in your browser and never uploads what you paste, which matters since a real API response is exactly what you would paste here.

Other JSON tools

Need the full set of options — sorting, indentation, table and tree views? The general JSON formatter has all of them in one place.