← Back to Blog

Convert JSON to TypeScript: Types and Interfaces Guide

Learn to convert JSON to TypeScript types and interfaces. Covers tools, best practices, and runtime validation with Zod.

Sarah Chen11 min readprogramming
S

Sarah Chen

Senior Software Engineer

Sarah is a full-stack software engineer with 8 years of experience in API development, TypeScript, and data engineering. She has designed and maintained large-scale JSON processing pipelines and contributes in-depth technical guides on performance optimisation, schema design, Python data workflows, and backend integration patterns.

TypeScriptAPI DevelopmentPythonData EngineeringJSON SchemaPerformance Tuning
11 min read

Why Convert JSON to TypeScript?

TypeScript types provide compile-time safety and better developer experience with autocomplete and error detection.

Quick Example

JSON Data

{

"id": 1,

"name": "Alice",

"email": "alice@example.com",

"active": true

}

TypeScript Interface

interface User {

id: number;

name: string;

email: string;

active: boolean;

}

Online Tools

  • Paste JSON
  • Select TypeScript
  • Copy generated types
  • Options:

    • Interface vs Type
    • Optional properties
    • Runtime validation

    Type Mapping

    | JSON Type | TypeScript Type |

    |-----------|-----------------|

    | string | string |

    | number | number |

    | true/false | boolean |

    | null | null |

    | array | T[] or Array |

    | object | interface or type |

    Handling Optional and Null

    interface User {
    

    name: string;

    middleName?: string; // Optional

    deletedAt: string | null; // Nullable

    nickname?: string | null; // Both

    }

    Arrays and Nested Objects

    interface Response {
    

    users: User[];

    metadata: {

    total: number;

    page: number;

    };

    }

    Using quicktype CLI

    # Install
    

    npm install -g quicktype

    # Generate types

    quicktype -s json -o types.ts data.json

    # From URL

    quicktype -s json -o types.ts "https://api.example.com/users"

    Runtime Validation with Zod

    import { z } from 'zod';
    
    

    const UserSchema = z.object({

    id: z.number(),

    name: z.string(),

    email: z.string().email(),

    active: z.boolean()

    });

    // Infer TypeScript type

    type User = z.infer<typeof UserSchema>;

    // Validate at runtime

    const result = UserSchema.safeParse(data);

    if (result.success) {

    const user: User = result.data;

    }

    Complex Types

    Union Types

    type Status = "pending" | "approved" | "rejected";
    
    

    interface Task {

    id: number;

    status: Status;

    }

    Generic Types

    interface ApiResponse<T> {
    

    data: T;

    error?: string;

    }

    type UserResponse = ApiResponse<User>;

    Type vs Interface

    Use Interface for:

    • Object shapes
    • Extending/inheritance
    • Public APIs (can be augmented)

    interface User {
    

    id: number;

    name: string;

    }

    interface AdminUser extends User {

    permissions: string[];

    }

    Use Type for:

    • Unions and intersections
    • Complex types
    • Function signatures
    • Mapped types

    type Status = "pending" | "approved" | "rejected";
    

    type Nullable<T> = T | null;

    type ReadOnly<T> = { readonly [K in keyof T]: T[K] };

    Automated Type Generation Workflow

    JSON to TypeScript Pipeline

    // scripts/generate-types.ts
    

    import { exec } from 'child_process';

    import { promisify } from 'util';

    import fs from 'fs/promises';

    const execAsync = promisify(exec);

    async function generateTypes() {

    // Fetch latest API schema

    const response = await fetch('https://api.example.com/schema.json');

    const schema = await response.json();

    // Save to temp file

    await fs.writeFile('temp-schema.json', JSON.stringify(schema, null, 2));

    // Generate TypeScript types

    await execAsync('quicktype -s json -l typescript -o src/types/api.ts temp-schema.json');

    // Clean up

    await fs.unlink('temp-schema.json');

    console.log('✅ Types generated successfully');

    }

    generateTypes();

    package.json Script

    {
    

    "scripts": {

    "types:generate": "ts-node scripts/generate-types.ts",

    "types:watch": "nodemon --watch api-schema.json --exec npm run types:generate"

    }

    }

    Best Practices

  • Use quicktype for initial conversion - saves time
  • Refine types manually - add business logic constraints
  • Add runtime validation with Zod - don't trust external data
  • Keep types close to usage - co-locate with components/services
  • Export types properly - use index.ts barrel exports
  • Version your types - track breaking changes
  • Document complex types - add JSDoc comments
  • Use strict TypeScript - enable all strict flags
  • Validate at API boundary - parse incoming data
  • Generate types from OpenAPI - for API-first development
  • Production Examples

    Express.js with Zod Validation

    import express from 'express';
    

    import { z } from 'zod';

    const CreateUserSchema = z.object({

    name: z.string().min(1).max(100),

    email: z.string().email(),

    age: z.number().int().positive().max(150),

    role: z.enum(['user', 'admin', 'moderator']).default('user')

    });

    type CreateUserDTO = z.infer<typeof CreateUserSchema>;

    app.post('/api/users', async (req, res) => {

    // Validate and parse

    const result = CreateUserSchema.safeParse(req.body);

    if (!result.success) {

    return res.status(400).json({

    error: 'Validation failed',

    details: result.error.flatten()

    });

    }

    const userData: CreateUserDTO = result.data;

    // Type-safe from here on

    const user = await db.users.create(userData);

    res.json(user);

    });

    Next.js API with Type Safety

    import { NextApiRequest, NextApiResponse } from 'next';
    

    import { z } from 'zod';

    const QuerySchema = z.object({

    page: z.string().regex(/^\d+$/).transform(Number).default('1'),

    limit: z.string().regex(/^\d+$/).transform(Number).default('10'),

    sort: z.enum(['name', 'createdAt', 'updatedAt']).optional()

    });

    type ProductListQuery = z.infer<typeof QuerySchema>;

    export default async function handler(

    req: NextApiRequest,

    res: NextApiResponse

    ) {

    const query = QuerySchema.parse(req.query);

    const products = await db.products.findMany({

    skip: (query.page - 1) query.limit,

    take: query.limit,

    orderBy: query.sort ? { [query.sort]: 'desc' } : undefined

    });

    res.json(products);

    }

    React Query with Type-Safe API

    import { useQuery } from '@tanstack/react-query';
    

    import { z } from 'zod';

    const UserSchema = z.object({

    id: z.number(),

    name: z.string(),

    email: z.string().email(),

    avatar: z.string().url().nullable()

    });

    type User = z.infer<typeof UserSchema>;

    const UsersResponseSchema = z.object({

    data: z.array(UserSchema),

    total: z.number()

    });

    async function fetchUsers(): Promise<z.infer<typeof UsersResponseSchema>> {

    const response = await fetch('/api/users');

    const json = await response.json();

    // Runtime validation

    return UsersResponseSchema.parse(json);

    }

    function UserList() {

    const { data, isLoading } = useQuery({

    queryKey: ['users'],

    queryFn: fetchUsers

    });

    if (isLoading) return <div>Loading...</div>;

    // data is fully typed here

    return data.data.map(user => (

    <div key={user.id}>

    {user.name} ({user.email})

    </div>

    ));

    }

    Advanced Patterns

    Branded Types for IDs

    type UserId = number & { readonly __brand: 'UserId' };
    

    type OrderId = number & { readonly __brand: 'OrderId' };

    function getUserId(id: number): UserId {

    return id as UserId;

    }

    function getUser(id: UserId): Promise<User> {

    // Type-safe - can't pass OrderId by mistake

    }

    const userId = getUserId(123);

    const orderId = 456 as OrderId;

    getUser(userId); // ✅ OK

    getUser(orderId); // ❌ Type error

    Discriminated Unions

    type SuccessResponse<T> = {
    

    success: true;

    data: T;

    };

    type ErrorResponse = {

    success: false;

    error: {

    code: string;

    message: string;

    };

    };

    type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;

    function handleResponse(response: ApiResponse<User>) {

    if (response.success) {

    // TypeScript knows response.data exists

    console.log(response.data.name);

    } else {

    // TypeScript knows response.error exists

    console.error(response.error.message);

    }

    }

    JSON Schema to TypeScript

    For OpenAPI/JSON Schema projects:

    # Install
    

    npm install -D json-schema-to-typescript

    # Generate from JSON Schema

    json2ts -i schema.json -o types.ts

    // schema.json
    

    {

    "$schema": "http://json-schema.org/draft-07/schema#",

    "type": "object",

    "properties": {

    "id": { "type": "number" },

    "name": { "type": "string" },

    "email": { "type": "string", "format": "email" }

    },

    "required": ["id", "name", "email"]

    }

    Generates:

    export interface Schema {
    

    id: number;

    name: string;

    email: string;

    }

    Tool Comparison

    | Tool | Runtime Validation | Type Generation | Bundle Size |

    |------|-------------------|-----------------|-------------|

    | quicktype | ❌ | ✅ Fast | 0kb |

    | Zod | ✅ | ✅ (infer) | 12kb |

    | io-ts | ✅ | ✅ (infer) | 15kb |

    | Yup | ✅ | ❌ | 18kb |

    | JSON Schema | ✅ | ✅ (separate) | 25kb+ |

    Recommendation: Use quicktype for initial generation + Zod for runtime validation.

    CI/CD Integration

    GitHub Actions Workflow

    ``.yaml

    name: Type Generation

    on:

    schedule:

    - cron: '0 2 ' # Daily at 2 AM

    workflow_dispatch:

    jobs:

    generate-types:

    runs-on: ubuntu-latest

    steps:

    - uses: actions/checkout@v3

    - name: Setup Node

    uses: actions/setup-node@v3

    with:

    node-version: '20'

    - name: Install dependencies

    run: npm ci

    - name: Generate types from API

    run: npm run types:generate

    - name: Commit changes

    run: |

    git config user.name 'GitHub Actions'

    git config user.email 'actions@github.com'

    git add src/types/

    git diff --quiet && git diff --staged --quiet || git commit -m "chore: update generated types"

    git push

    ``

    Conclusion

    Converting JSON to TypeScript types dramatically improves code quality and developer experience. Use quicktype for fast generation, Zod for runtime validation at API boundaries, and discriminated unions for type-safe error handling. Always validate external data at runtime — TypeScript's type safety only exists at compile time. For production APIs, integrate type generation into CI/CD pipelines to keep types synchronized with backend schemas automatically.

    Share:

    Related Articles