jsontypescriptinterfacesapi

JSON to TypeScript: Auto-generate Interfaces

·8 min read·Converters

Why Generate TypeScript Interfaces from JSON?

When you integrate a REST API, you receive JSON responses. Writing TypeScript interfaces by hand for every endpoint is tedious, error-prone, and inevitably gets out of sync as APIs evolve. An automatic generator reads the actual JSON response and produces accurate, named interfaces in seconds — saving hours across a large codebase.

How the Generator Works

The generator: 1. Parses the JSON value 2. Infers the TypeScript type for each key (string, number, boolean, null, nested interface, array) 3. Recursively processes nested objects — each becomes its own named interface 4. Inspects array contents — an array of strings becomes string[], array of objects becomes a named typed array 5. Outputs all interfaces in dependency order: leaf types first, root type last (or first, depending on style)

Complete Generated Output Example

Input JSON (real API response structure):

json
{
  "user": {
    "id": 1,
    "name": "Ravi Mehta",
    "email": "ravi@example.com",
    "active": true,
    "score": 98.5,
    "roles": ["admin", "editor"],
    "address": {
      "city": "Surat",
      "state": "Gujarat",
      "pincode": "395001"
    }
  },
  "meta": {
    "requestId": "req_xyz123",
    "timestamp": "2025-06-01T10:30:00Z"
  }
}

Generated TypeScript interfaces:

typescript
interface Address {
  city: string;
  state: string;
  pincode: string;
}

interface User {
  id: number;
  name: string;
  email: string;
  active: boolean;
  score: number;
  roles: string[];
  address: Address;
}

interface Meta {
  requestId: string;
  timestamp: string;
}

interface Root {
  user: User;
  meta: Meta;
}

JSON to TypeScript Type Mapping

JSON valueTypeScript typeNotes
"hello"string
42, 3.14numberBoth int and float → number
true, falseboolean
nullnullWiden to T | null manually
[]unknown[]No element type info
["a","b"]string[]
[1,"x"](number | string)[]Union type
{}Named interfaceRecursive

interface vs type — Which to Use?

typescript
// interface — preferred for object shapes; supports declaration merging
interface User {
  id: number;
  name: string;
}

// type alias — preferred for unions, primitives, and computed types
type UserId = number;
type Status = "active" | "inactive" | "pending";
type ApiResponse<T> = { data: T; meta: Meta };

For API response types, use interface for objects. Use type for union types, string enums, and generic wrappers.

Optional Fields and Nullable Types

The generator creates required fields by default. After generation, add ? for optional fields and | null for nullable fields based on the real API contract:

typescript
interface User {
  id: number;
  name: string;
  email: string;
  middleName?: string;           // optional — may be absent
  bio: string | null;            // nullable — present but may be null
  deletedAt: string | null;      // nullable timestamp
  avatarUrl?: string | null;     // optional AND nullable
}

Generic Wrapper for API Responses

Most APIs wrap responses in a consistent envelope. Create a generic wrapper once:

typescript
interface ApiResponse<T> {
  data: T;
  success: boolean;
  message?: string;
  requestId: string;
}

interface PaginatedResponse<T> {
  data: T[];
  pagination: {
    page: number;
    limit: number;
    total: number;
    totalPages: number;
  };
}

// Usage:
type UserResponse = ApiResponse<User>;
type UserListResponse = PaginatedResponse<User>;

Type Guards for Runtime Safety

TypeScript interfaces are erased at runtime — they provide no runtime protection. Add a type guard for untrusted data:

typescript
function isUser(obj: unknown): obj is User {
  return (
    typeof obj === "object" &&
    obj !== null &&
    typeof (obj as User).id === "number" &&
    typeof (obj as User).name === "string" &&
    typeof (obj as User).email === "string"
  );
}

async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const json: unknown = await response.json();

  if (!isUser(json)) {
    throw new Error("Unexpected API response shape");
  }
  return json; // now typed as User
}

For more robust runtime validation, use Zod (see the JSON to Zod guide).

Using Generated Interfaces with fetch

typescript
// Place generated types in src/types/api.ts
import type { Root, User } from "./types/api";

async function getUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const data = (await response.json()) as Root;
  return data.user;
}

// TypeScript now knows data.user.address.city is a string
const user = await getUser(42);
console.log(user.address.city.toUpperCase()); // fully typed

Best Practices

  1. Use the actual API response as input, not a hand-crafted example — real data catches types you might miss
  2. Generate after every API contract change — keep types in sync automatically
  3. Store in a dedicated filesrc/types/api.ts or co-located with the API module
  4. Review null fields — change field: null to field: string | null based on the API spec
  5. Add optional markers — fields absent in some responses should be field?: type
  6. Run tsc after generating — catch any type mismatches immediately

Use JSONKit's JSON to TypeScript tool to generate interfaces from any JSON response instantly — paste, generate, copy, paste into your codebase.

Try JSON to TypeScript

Generate TypeScript interfaces from any JSON object automatically.