Guide
How to convert JSON to TypeScript types
A practical walkthrough for turning a real JSON response into TypeScript interfaces, then deciding whether plain types are enough or runtime validation should come next.
Need it now? JSON → TypeScript converter.
Step 1 — Start with a representative JSON sample
Use a real API response, webhook payload, or fixture. Include nested objects and at least one item in each array so the generated type has enough information to work with.
{
"id": 42,
"name": "Ada Lovelace",
"email": "ada@example.com",
"isAdmin": false,
"profile": {
"company": "Analytical Engine Labs",
"timezone": "Europe/London"
},
"tags": ["math", "engine"]
}Step 2 — Generate TypeScript interfaces
Paste the sample into the JSON → TypeScript converter. The first pass gives you clean structural types:
export interface Root {
id: number;
name: string;
email?: string;
isAdmin: boolean;
profile: Profile;
tags: string[];
}
export interface Profile {
company: string;
timezone: string;
}Step 3 — Rename and tighten the output
Generated names are a starting point. Rename the root type, review nested types, and check optional fields against your real API contract.
export interface User {
id: number;
name: string;
email?: string;
isAdmin: boolean;
profile: UserProfile;
tags: string[];
}
export interface UserProfile {
company: string;
timezone: string;
}Step 4 — Use the type at your API boundary
TypeScript helps your editor and compiler, but it does not validate JSON at runtime. This pattern is fine when you trust the upstream response:
export async function fetchUser(): Promise<User> {
const res = await fetch("/api/me");
return (await res.json()) as User;
}Step 5 — Upgrade to Zod when runtime validation matters
If the JSON comes from a third-party API, user form, webhook, env var, or localStorage, TypeScript alone cannot protect you. Generate a Zod schema instead and derive the type from that schema:
import { z } from "zod";
export const User = z.object({
id: z.number(),
name: z.string(),
email: z.string().email().optional(),
isAdmin: z.boolean(),
profile: z.object({
company: z.string(),
timezone: z.string(),
}),
tags: z.array(z.string()),
});
export type User = z.infer<typeof User>;Plain TypeScript or Zod?
- • Use TypeScript when the data is internal, already typed, or only used for editor support.
- • Use Zod when data crosses a trust boundary and can be malformed at runtime.
- • Use JSON Schema or OpenAPI input when you have a formal contract instead of just one sample.
Common pitfalls
- • One sample is not the whole API. A field missing from this sample might still exist later; a field present here might be optional in production.
- • Empty arrays become weak types. Use a sample with at least one array item to infer the element shape.
- • Dates are strings over the wire. JSON has no Date type. Keep timestamps as string unless your code explicitly parses them.
- • Types are not validators. If bad JSON can crash a flow, use the JSON to Zod guide after generating the first type.
Troubleshooting generated TypeScript types
TypeScript types generated from JSON are only as complete as the sample. Before using them as an API contract, check the cases that a single payload cannot prove.
Optional, nullable, or both?
A missing field and a field set to null mean different things in TypeScript. Compare the generated type against API docs, production fixtures, or more than one response sample.
// If this field may be omitted: email?: string; // If the API sends null explicitly: email: string | null; // If both can happen: email?: string | null;
Empty arrays need real examples
If an array is empty in the sample, the converter cannot infer the element shape. Paste a payload with at least one item, or tighten the generated type by hand.
// Empty sample arrays cannot reveal the element type
const sample = { tags: [] };
// Weak first pass
interface User {
tags: unknown[];
}
// Tighten it after checking real data
interface User {
tags: string[];
}Dates are strings over the wire
JSON has no native date type. Keep timestamps as strings in boundary types, then parse them where your app needs date operations.
interface Invoice {
id: string;
// JSON sends this as a string, even when it represents a date
issuedAt: string;
}
const issuedAt = new Date(invoice.issuedAt);Type assertions are not validation
as User can make the compiler quiet, but it cannot catch a broken API response. Use it only when you trust the source; otherwise upgrade the boundary to Zod.
// This only tells TypeScript to trust you. // It does not validate the response at runtime. const user = (await res.json()) as User;
FAQ
Use TypeScript for trusted internal data. Use Zod when you need runtime validation for external input.
No. Review API docs or paste multiple representative samples into your own checklist before finalizing optional fields.
If you have a formal contract, use JSON Schema → TypeScript or OpenAPI → TypeScript instead of inferring from a single sample.
No. It only tells TypeScript to trust the value. If the JSON comes from an API, form, webhook, localStorage, or env var, validate it with Zod or another runtime schema.
Use string for the boundary type. Parse into Date only after the JSON has entered your application code.