Every time you call an API in TypeScript and write as any, you are trading away type safety for convenience. The fix is straightforward: define an interface that matches the JSON response structure. But for large, nested payloads, writing those interfaces by hand is tedious and error-prone.
This guide explains how JSON maps to TypeScript types, how to use an automatic generator to bootstrap your interfaces, and how to refine the output for production use.
Why Type Your API Responses?
Without explicit types, TypeScript loses its two biggest advantages: compile-time error detection and IDE autocomplete.
// ❌ any disables all type checking
const data: any = await fetch('/api/user').then(r => r.json());
console.log(data.nmae); // Typo — no error, fails silently at runtime
// ✅ Typed response — typos caught at compile time
interface User { id: number; name: string; email: string; }
const data = await fetch('/api/user').then(r => r.json()) as User;
console.log(data.nmae); // Error: Property 'nmae' does not exist on type 'User'
Beyond error detection, typed responses give you autocomplete on every field — no more switching to Postman to remember whether it is createdAt or created_at.
How JSON Maps to TypeScript
| JSON Value | TypeScript Type |
|---|---|
string | string |
number (int or float) | number |
true / false | boolean |
null | null or optional ? |
[ ... ] (array) | Type[] |
{ ... } (object) | Nested interface |
Simple Object
// Input JSON
{
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"isActive": true,
"score": 98.5
}
// Generated TypeScript
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
score: number;
}
Nested Objects
// Input JSON
{
"id": 1,
"name": "Alice",
"address": {
"street": "123 Main St",
"city": "Portland",
"zip": "97201"
},
"tags": ["premium", "verified"]
}
// Generated TypeScript
interface Address {
street: string;
city: string;
zip: string;
}
interface User {
id: number;
name: string;
address: Address;
tags: string[];
}
Handling null Values
// Input JSON
{
"id": 1,
"name": "Alice",
"deletedAt": null,
"bio": null
}
// Option 1: nullable union
interface User {
id: number;
name: string;
deletedAt: string | null;
bio: string | null;
}
// Option 2: optional (null → ?)
interface User {
id: number;
name: string;
deletedAt?: string;
bio?: string;
}
Using the JSON to TypeScript Generator
The JSON to TypeScript Interface Generator converts any JSON into TypeScript interfaces in real time. Paste a JSON sample and choose your options:
- Interface vs Type alias: generates
interface Foo { ... }ortype Foo = { ... } - readonly: adds
readonlyto every property — useful for immutable API response objects - null → optional: converts
nullvalues to optional?instead of| null - Nested interfaces: extracts nested objects into named interfaces for readability
- Sample presets: User, Product, and Order samples to explore the tool instantly
Practical Patterns
Generic API Response Wrapper
// Most APIs wrap responses in a common envelope
{
"success": true,
"data": { ... },
"meta": { "total": 100, "page": 1, "perPage": 20 }
}
// After generation, add generics to make it reusable
interface Meta {
total: number;
page: number;
perPage: number;
}
interface ApiResponse {
success: boolean;
data: T;
meta: Meta;
}
// Usage
const res = await fetch('/api/users').then(r => r.json()) as ApiResponse;
Combining with Zod for Runtime Validation
import { z } from 'zod';
// Use the generator to sketch the shape, then write the Zod schema
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
isActive: z.boolean(),
});
type User = z.infer; // TypeScript type derived from schema
// Fetch + validate + type in one step
const data = UserSchema.parse(await fetch('/api/user').then(r => r.json()));
Typing localStorage Data
interface AppSettings {
theme: 'light' | 'dark';
language: string;
notifications: boolean;
}
function loadSettings(): AppSettings | null {
const raw = localStorage.getItem('settings');
if (!raw) return null;
return JSON.parse(raw) as AppSettings;
}
Refining Auto-Generated Types
Auto-generated interfaces are a starting point, not a final product. A few common improvements:
- Empty arrays:
[]may be inferred asnever[]orunknown[]— replace with the actual element type - Date strings: ISO date strings are typed as
string— add a comment or parse toDate - Union types: narrow
status: stringtostatus: 'active' | 'inactive' | 'banned'for exhaustive checks - Interface names: rename generic names (
RootObject,Item) to domain-meaningful names - Generics: extract pagination wrappers or API envelopes into reusable generic types
FAQ
- Should I use interface or type?
- Both work for object shapes. The TypeScript team recommends
interfacefor object types that may be extended, andtypefor union types, intersection types, and mapped types. Pick one and be consistent within a codebase. - What happens when the array in JSON is empty?
- An empty array
[]has no elements to infer the type from, so tools may outputnever[]orunknown[]. Use a representative API response that contains at least one array element for best results. - When should I use Zod instead of plain interfaces?
- Use Zod when you need runtime validation — i.e., when the data comes from an external source (API, user input, localStorage) and you cannot guarantee its shape at compile time. For internal data you fully control, plain interfaces are sufficient and have zero runtime cost.
- How do I type an API response in fetch without casting?
fetchreturnsResponseand.json()returnsPromise<any>. You need to cast or validate. A common pattern:const data = (await resp.json()) as User. For safety, validate with Zod before casting.- What about OpenAPI / Swagger?
- If your API has an OpenAPI spec, tools like
openapi-typescriptcan generate TypeScript types directly from the spec — more reliable than inferring from a single JSON sample. The JSON-to-TypeScript approach is best when you only have a response payload (no spec), or for rapid prototyping.
Summary
- Typing API responses unlocks TypeScript's full power: compile-time error detection and IDE autocomplete on every field.
- JSON maps directly to TypeScript: strings →
string, numbers →number, objects →interface, arrays →T[]. - Use the JSON to TypeScript Generator to bootstrap interfaces from any JSON payload.
- Refine auto-generated types: narrow string unions, name interfaces meaningfully, and add generics for reusable wrappers.
- For runtime validation, pair TypeScript interfaces with Zod — derive the type from the schema using
z.infer.