Why Generate Types from JSON

You're calling an API. The response is a 47-field nested JSON blob. Writing the TypeScript interface by hand takes 20 minutes and you'll get a couple fields wrong. A generator does it in under a second. The savings compound across every endpoint.

That said, auto-generated types are a starting point. They tell you the shape based on one sample response. They can't tell you which fields are optional, which fields are unions, or which numeric strings are actually IDs. You still have to think.

Quick Start

Paste your JSON into the JSON to TypeScript converter. It produces interfaces for every object, types nested objects into separate interfaces, and arrays into typed lists. Example input:

{ "id": 1, "name": "John Doe", "email": "john@example.com", "active": true, "address": { "street": "123 Main St", "city": "Springfield" }, "tags": ["admin", "user"]
}

Output:

export interface Address { street: string; city: string;
} export interface Root { id: number; name: string; email: string; active: boolean; address: Address; tags: string[];
}

Refine the Output

Three things to do every time after generating:

1. Mark optional fields. Sample JSON probably has every optional field present. Check the API docs and add ?:

export interface User { id: number; name: string; email?: string; // sometimes missing avatar_url?: string; // optional middle_name: string | null; // present but nullable
}

Note the distinction: email?: string means the field might be absent. middle_name: string | null means the field is always present but might be null. APIs differ — match what they actually return.

2. Replace any with real types. Generators emit any when they encounter null or empty arrays. Replace these with the actual type:

// Generated:
permissions: any[]; // sample had [] // Refined:
permissions: ("read" | "write" | "admin")[];

3. Use union types for variant fields. When a field can be one of several known values:

status: "pending" | "active" | "suspended"; // not just string

Interface vs Type

Both can describe object shapes. Pick based on intent:

// interface — for object shapes you might extend
interface User { id: number; name: string;
}
interface Admin extends User { permissions: string[];
} // type — for unions, intersections, primitives, tuples
type ID = string | number;
type UserOrGuest = User | Guest;
type Coord = [number, number];

For generated JSON types, interface is the conventional choice. Switch to type when you need unions or aliases.

Runtime Validation

TypeScript types vanish at runtime. The compiler trusts you when you say "this fetch returns a User" — but if the API returns garbage, your code crashes in production with a useless error. The fix is runtime validation:

import { z } from "zod"; const UserSchema = z.object({ id: z.number(), name: z.string(), email: z.string().email().optional(), active: z.boolean(),
}); type User = z.infer<typeof UserSchema>; // type is derived from schema const data = await fetch("/api/user").then(r => r.json());
const user = UserSchema.parse(data); // throws if invalid

Now the type and the runtime check are the same source of truth. If the API changes shape, you find out at the boundary instead of three function calls deeper.

If the API Has OpenAPI/Swagger

Skip the JSON-sample workflow entirely. Use openapi-typescript to generate types directly from the API's schema. This produces accurate types with optional fields, enums, and unions correctly modeled — no guessing from sample responses required.

npx openapi-typescript https://api.example.com/openapi.json -o api-types.ts

A Practical Workflow

  1. Capture a real API response (Network tab → right-click → copy response).
  2. Paste into the generator.
  3. Refine: mark optional fields, replace any, add literal unions for known enums.
  4. Wrap in a Zod schema for runtime validation at the API boundary.
  5. Use the inferred type everywhere downstream.

This takes 5 minutes per endpoint and catches roughly 95% of the type-mismatch bugs that would otherwise show up in production. Combined with the patterns in the async/await guide, you get end-to-end type safety from API call to component render.