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 Chen
• Senior Software EngineerSarah 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.
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
quicktype.io (Recommended)
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
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.
Related Articles
What is JSON? Complete Guide for Beginners 2026
Learn what JSON is, its syntax, data types, and use cases. A comprehensive beginner-friendly guide to understanding JavaScript Object Notation.
JavaScript JSON: Parse, Stringify, and Best Practices
Complete guide to JSON in JavaScript. Learn JSON.parse(), JSON.stringify(), error handling, and advanced techniques for web development.
Understanding JSON Schema: Complete Validation Guide
Master JSON Schema for data validation. Learn schema syntax, validation techniques, and implementation across different programming languages.