Introduction

Working with JSON data in TypeScript requires type definitions for type safety and better developer experience. Manually writing interfaces is tedious and error-prone. This guide shows how to generate TypeScript interfaces from JSON automatically.

Why Generate Interfaces?

Problems with Manual Typing

// ❌ Manual typing - error-prone
interface User {
  id: number;
  name: string;
  email: string;
  address: {
    street: string;
    city: string;
    zipCode: string;
  };
  hobbies: string[];
}

// What if the API changes?
// What if you miss a field?
// What if types are wrong?

Benefits of Generation

Accuracy: Matches actual JSON structure ✅ Speed: Instant generation ✅ Maintenance: Easy to regenerate when API changes ✅ Type Safety: Catch errors at compile time

Basic Interface Generation

Simple JSON to Interface

Input JSON:

{
  "id": 1,
  "name": "Alice",
  "email": "alice@example.com",
  "age": 30,
  "active": true
}

Generated Interface:

interface Root {
  id: number;
  name: string;
  email: string;
  age: number;
  active: boolean;
}

Nested Objects

Input JSON:

{
  "user": {
    "id": 1,
    "name": "Alice",
    "address": {
      "street": "123 Main St",
      "city": "Stockholm"
    }
  }
}

Generated Interface:

interface Root {
  user: User;
}

interface User {
  id: number;
  name: string;
  address: Address;
}

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

Arrays

Input JSON:

{
  "users": [
    {
      "id": 1,
      "name": "Alice"
    },
    {
      "id": 2,
      "name": "Bob"
    }
  ]
}

Generated Interface:

interface Root {
  users: User[];
}

interface User {
  id: number;
  name: string;
}

Advanced Features

Optional Properties

Input JSON:

{
  "name": "Alice",
  "email": "alice@example.com"
}

Generated Interface (strict):

interface Root {
  name: string;
  email: string;
}

Generated Interface (with nullables):

interface Root {
  name: string;
  email: string;
  phone?: string;
  address?: Address;
}

Union Types

Input JSON:

{
  "status": "active",
  "value": 42
}

With multiple examples:

interface Root {
  status: "active" | "inactive" | "pending";
  value: number | string;
}

Enums

Input JSON:

{
  "status": "success",
  "type": "user"
}

Generated:

enum Status {
  Success = "success",
  Error = "error",
}

enum Type {
  User = "user",
  Admin = "admin",
}

interface Root {
  status: Status;
  type: Type;
}

Implementation Examples

Online Tools

Use our tool:

Programmatic Generation

TypeScript:

function jsonToInterface(json: any, name: string = "Root"): string {
  let interfaceStr = `interface ${name} {\n`;

  for (const [key, value] of Object.entries(json)) {
    const type = inferType(value);
    interfaceStr += `  ${key}: ${type};\n`;
  }

  interfaceStr += "}";
  return interfaceStr;
}

function inferType(value: any): string {
  if (value === null) return "null";
  if (Array.isArray(value)) {
    const itemType = value.length > 0 ? inferType(value[0]) : "any";
    return `${itemType}[]`;
  }
  if (typeof value === "object") {
    return "object"; // Would need recursive generation
  }
  return typeof value;
}

Best Practices

✅ Do This

1. Use generated interfaces as starting point

// Generated
interface ApiResponse {
  data: User[];
  meta: Meta;
}

// Refine manually
interface ApiResponse {
  data: User[];
  meta: {
    count: number;
    page: number;
    totalPages: number;
  };
}

2. Add JSDoc comments

interface User {
  /** User's unique identifier */
  id: number;
  /** User's full name */
  name: string;
  /** User's email address */
  email: string;
}

3. Use utility types

// Make all fields optional
type PartialUser = Partial<User>;

// Pick specific fields
type UserSummary = Pick<User, "id" | "name">;

// Omit fields
type UserWithoutEmail = Omit<User, "email">;

❌ Don’t Do This

1. Don’t trust generated types blindly

// Generated might assume number
interface User {
  id: number;
}

// But API might return string IDs
// Always verify with actual API responses

2. Don’t generate from single example

// ❌ Single example might miss optional fields
// ✅ Use multiple examples or API schema

Integration with APIs

Fetch with Types

interface User {
  id: number;
  name: string;
  email: string;
}

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

Axios with Types

interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

async function getUser(id: number): Promise<ApiResponse<User>> {
  const response = await axios.get<ApiResponse<User>>(`/api/users/${id}`);
  return response.data;
}

Alternative: Zod Schemas

Zod provides runtime validation + TypeScript types:

import { z } from "zod";

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});

type User = z.infer<typeof UserSchema>;

// Runtime validation
const user = UserSchema.parse(jsonData);

Benefits:

  • Runtime validation
  • Type inference
  • Better error messages

Use our tool:

Conclusion

Generating TypeScript interfaces from JSON:

Benefits:

  • Type safety
  • Better IDE support
  • Catch errors early
  • Documentation

Remember:

  • Generate as starting point
  • Refine manually for accuracy
  • Keep in sync with API changes
  • Consider Zod for runtime validation

Next Steps