Generate TypeScript Types From JSON: My 5 Minute Fix

How do you trust a TypeScript type when the API itself doesn't know what it's returning?

Last March, I inherited a frontend codebase that talked to a legacy Rails API. The API had been through three teams, two rewrites, and one person who really loved returning null where a string should be. Every API call in the frontend was typed as any. Every single one. The codebase had 312 API call sites, and not one of them had a type definition that matched what the server actually returned.

I spent four days manually writing interfaces. By day two, I realized I was guessing. The API documentation was 18 months out of date. The actual JSON responses had fields the docs didn't mention, fields the docs said existed but didn't, and fields that were sometimes strings and sometimes numbers depending on which environment you hit.

This is the story of how I stopped guessing and started generating accurate TypeScript type definitions from real API JSON responses — and what I learned about where conversion tools help and where they quietly lie to you.

How do you generate TypeScript types from JSON API responses?

The basic workflow is simple. You capture a real JSON response from your API, paste it into a TypeScript converter, and get back an interface or type literal that mirrors the structure. Most TypeScript converters — including the one on this site — take raw JSON and produce a type tree that matches the shape of what you pasted.

Here's what I did with that Rails API. I opened my browser's network tab, hit every endpoint the frontend used, and copied the JSON response bodies into a converter. For a user profile endpoint, the JSON looked something like:

{
  "id": 8472,
  "email": "sarah@company.com",
  "profile": {
    "firstName": "Sarah",
    "lastName": "Okafor",
    "avatarUrl": "https://cdn.company.com/avatars/8472.png",
    "role": "admin"
  },
  "createdAt": "2023-03-14T09:22:11Z",
  "lastLoginAt": null
}

The converter produced:

interface ApiResponse {
  id: number;
  email: string;
  profile: {
    firstName: string;
    lastName: string;
    avatarUrl: string;
    role: string;
  };
  createdAt: string;
  lastLoginAt: null;
}

That's a starting point. But notice the last line: lastLoginAt: null. That's technically accurate for this one response — but it's wrong as a type definition. The field isn't always null. It's null when the user hasn't logged in yet, and it's a date string when they have. A single JSON sample can't tell you that.

This is the first lesson: a converter tells you what the JSON is, not what the type should be.

What makes a TypeScript type definition "accurate"?

An accurate type definition isn't one that matches a single response. It's one that matches every possible response the API can return. That distinction matters enormously.

When I ran my 312 API call sites through a converter, I got types that were syntactically correct but semantically incomplete. Out of 312 endpoints, 89 had at least one field where the type from a single sample was misleading. The most common issues fell into three buckets.

Null vs. undefined vs. missing keys

The Rails API returned null for empty fields. But some endpoints simply omitted fields when they had no value. A converter seeing {"name": "Sarah", "title": null} types title as null. A converter seeing {"name": "Sarah"} doesn't include title at all. Neither output is wrong, but neither is complete. The accurate type is title: string | null — and you only know that if you've seen both shapes.

String unions masquerading as generic strings

The role field in that user profile came back as "admin". The converter typed it as string. But after checking the API source, the only possible values were "admin", "member", and "guest".

Why one pasted response can never produce an optional field

The converter has an "optional" behavior switched on by default. It still will not give you a single question mark from a single response, and the reason is one line of arithmetic rather than a bug.

When the tool builds an interface it looks at how many objects it was given (objs.length) and how many of them actually contained the key (vals.length). It marks the field optional only when vals.length is smaller than objs.length — that is, when at least one object was missing the key. Paste one object and both numbers are 1, so 1 < 1 is false and every field comes out required.

That is the whole reason my first pass produced types that compiled cleanly and still crashed in production. A single sample describes one shape; an interface has to describe every shape.

The two-sample trick that actually gets you optional fields

The fix is not a setting. It is feeding the converter two objects at once, wrapped in a JSON array. The tool merges the members of an array into one type, so anything present in the first object but absent from the second gets a question mark, and anything whose value differs gets a union.

Practical way to collect the second sample: hit the same endpoint twice at different moments — before and after a user logs in, with an empty cart and with a full one, or against two accounts with different permission levels. Then wrap both bodies:

[
  { "id": 8472, "email": "sarah@company.com", "lastLoginAt": null },
  { "id": 8472, "email": "sarah@company.com", "lastLoginAt": "2026-04-02T08:11:00Z", "middleName": "Ada" }
]

The merged result is materially different from the single-sample one:

interface Root {
  id: number;
  email: string;
  lastLoginAt: string | null;
  middleName?: string;
}

Three things changed at once. lastLoginAt stopped being the literal null and became string | null, because the tool saw both a null and a string. middleName appeared at all. And middleName carries a ?, because it was missing from one of the two objects.

Note the order of the union members: string | null is emitted alphabetically, not in the order the values appeared. The tool collects the candidate types in a set and sorts them, so you will see number | string rather than string | number. It is cosmetic, but it is worth knowing before you assume the tool is ignoring your data.

What the converter silently does to your keys and nested objects

Four habits of the generator produce output that surprises people the first time:

  • A nested object becomes its own interface, named after the key. A profile object inside the response is lifted out into a separate interface Profile and referenced by name, rather than being inlined as an anonymous brace. The name is the key run through PascalCase, so shipping_address becomes ShippingAddress.
  • Two structurally identical objects share one interface. The generator fingerprints each interface by its sorted field list and reuses an existing name when the fingerprint matches, so two copies of the same address block do not produce Address and Address2.
  • An array of objects is named <Key>Item. A packages array does not become Package[]; its element interface is called PackagesItem. If you expected singular names you will want to rename after pasting.
  • Keys that are not valid identifiers are quoted. A key like user-id cannot appear bare in TypeScript, so the generator emits "user-id": string;. Keys that begin with a digit are handled a different way: the derived type name is prefixed so the file still compiles.

Two more edge cases worth checking by eye. An empty array in your sample produces unknown[] — from [] alone there is genuinely no element type to infer, and unknown[] is the honest answer rather than a guess. And a field whose every observed value is null is typed as the literal null, which is almost never what you want: it is a signal that your sample set is too small, not a description of the field.

A five-minute audit before the types go into your repo

  1. Paste at least two responses for any endpoint whose shape you are unsure about.
  2. Read every literal null and ask whether the field is really always null. If not, find a sample where it is populated and merge that in.
  3. Read every unknown[] and every [ ] in the sample — an empty array in the sample is the usual cause.
  4. Search the output for any. The generator never emits it, so a hit means a hand edit crept in.
  5. Keep the JSON you pasted in the repo next to the generated file. The next time the API changes you re-run the same input instead of re-discovering the shape from scratch.

None of this takes longer than the paste itself. It just replaces the guesswork that made the first version of those 312 call sites wrong.

Frequently Asked Questions

How do I convert a JSON API response to a TypeScript interface?

You can easily convert a JSON API response by pasting it into an online TypeScript converter tool, which instantly generates the required interfaces. These tools analyze your JSON structure and output accurate type definitions that you can copy directly into your project.

What is the best tool to generate TypeScript types from JSON?

The best tools for generating TypeScript types from JSON are online converters that offer deep customization options like handling nested objects and arrays. They ensure your types perfectly match your API data by automatically inferring the correct primitive types for each key-value pair.

Can I automatically generate TypeScript interfaces from a REST API?

Yes, by taking the JSON response from your REST API endpoint and feeding it into a TypeScript type generator. Some advanced CLI packages can even fetch the API endpoint directly and output a fully typed TypeScript file without requiring manual copy-pasting.

How do I handle nested JSON objects when converting to TypeScript?

Most modern TypeScript converters automatically detect nested JSON objects and generate separate, appropriately named interfaces for them. This ensures your code remains readable and strictly typed, even when dealing with complex and deeply nested API payloads.

How do I convert JSON arrays to TypeScript types?

When you input a JSON array into a TypeScript converter, it will typically extract the first object's structure and define an array of that specific type. For example, a JSON array of users will be converted to a User[] interface, giving you full type safety over list iterations.

How to infer TypeScript types from raw JSON?

Inferring TypeScript types from raw JSON involves analyzing the data types of each key-value pair in the payload to determine if they are strings, numbers, or booleans. By using an online converter, you can instantly infer these types and generate a precise type definition without manual inspection.

How do I generate TypeScript types from Postman JSON responses?

Simply copy the JSON response body from your Postman request and paste it into a JSON-to-TypeScript converter tool. The tool will instantly provide the TypeScript interfaces needed to strongly type your API client, saving you from writing boilerplate code manually.

Are online JSON to TypeScript converters accurate for production use?

Yes, reputable online JSON to TypeScript converters are highly accurate and safe for production use because they strictly parse data types according to TypeScript compiler rules. However, you should always review the generated types for edge cases, such as optional fields or null values, before committing them.

How to make JSON properties optional in generated TypeScript types?

Many TypeScript converter tools offer a setting to automatically add a question mark to properties that might be missing or null in the JSON response. Alternatively, you can manually adjust the generated interfaces to mark specific fields as optional based on your API documentation.