Every TypeScript developer hits the same wall: you get a JSON response from an API and need proper types for it. Without types, you lose autocompletion, refactoring safety, and compile-time error checks. With them, your editor becomes a superpower.
But how should you actually generate those types? There is no single right answer. The best approach depends on how large the JSON payload is, whether the schema changes frequently, and whether you need runtime validation on top of static types.
In this guide, I will walk through five practical approaches to converting JSON into TypeScript interfaces, with code examples and honest trade-offs for each.
Here is the sample JSON we will use throughout:
{
"id": 101,
"name": "Alice Chen",
"email": "alice@example.com",
"role": "admin",
"projects": [
{
"projectId": "p-200",
"title": "Dashboard Redesign",
"status": "active",
"tags": ["ui", "frontend"]
}
],
"meta": {
"lastLogin": "2026-02-20T08:30:00Z",
"twoFactorEnabled": true
}
}
1. Manual Typing
The most straightforward method: read the JSON, write the interfaces by hand.
interface Project {
projectId: string;
title: string;
status: string;
tags: string[];
}
interface UserMeta {
lastLogin: string;
twoFactorEnabled: boolean;
}
interface User {
id: number;
name: string;
email: string;
role: string;
projects: Project[];
meta: UserMeta;
}
You can refine things further. For example, role could be a union type "admin" | "editor" | "viewer" and status could be "active" | "archived" | "draft". Manual typing gives you full control over those decisions.
When to use it: Small payloads with fewer than 10 fields, one-off scripts, or situations where you need very precise union types and optional markers that no generator can infer from a single sample.
Pros:
- Zero dependencies
- Full control over naming, optional fields, and union types
- Easy to add JSDoc comments inline
Cons:
- Tedious for large or deeply nested JSON
- Error-prone (typos, missed fields)
- Must be updated manually when the API changes
2. json2ts CLI
json2ts is a lightweight npm package that reads JSON from stdin or a file and prints TypeScript interfaces to stdout. It fits neatly into existing build pipelines.
Installation and usage
npm install -g json-ts
Pipe JSON directly:
echo '{"id":101,"name":"Alice Chen","email":"alice@example.com","role":"admin","projects":[{"projectId":"p-200","title":"Dashboard Redesign","status":"active","tags":["ui","frontend"]}],"meta":{"lastLogin":"2026-02-20T08:30:00Z","twoFactorEnabled":true}}' | json-ts
Or point it at a file:
json-ts --file response.json
The output looks something like this:
interface RootObject {
id: number;
name: string;
email: string;
role: string;
projects: Project[];
meta: Meta;
}
interface Project {
projectId: string;
title: string;
status: string;
tags: string[];
}
interface Meta {
lastLogin: string;
twoFactorEnabled: boolean;
}
You would typically rename RootObject to User and potentially tighten the string types, but this saves a huge amount of initial typing.
When to use it: Medium-sized payloads, CI pipelines where you want to auto-generate types from fixture files, or when you prefer staying in the terminal.
Pros:
- Fast, scriptable, fits into Makefiles and CI
- Handles nested objects and arrays well
- Lightweight with minimal dependencies
Cons:
- Generated names are generic (
RootObject,Meta) and need renaming - Cannot infer union types, enums, or optional fields from a single sample
- Limited configuration options compared to quicktype
3. quicktype
quicktype is the most full-featured code generator in this space. It can infer types from JSON, JSON Schema, or GraphQL queries, and it targets over a dozen languages including TypeScript.
Installation
npm install -g quicktype
Generate interfaces from a JSON file
quicktype --src response.json --lang ts --just-types --top-level User
Output:
export interface User {
id: number;
name: string;
email: string;
role: string;
projects: ProjectItem[];
meta: Meta;
}
export interface ProjectItem {
projectId: string;
title: string;
status: string;
tags: string[];
}
export interface Meta {
lastLogin: string;
twoFactorEnabled: boolean;
}
The --just-types flag tells quicktype to emit only interfaces, without the converter classes it generates by default. The --top-level User flag sets the root interface name.
If you feed quicktype multiple JSON samples of the same schema, it can infer which fields are optional (present in some samples but not others) and which values form a union. That is a significant advantage over tools that look at a single example.
Advanced: generate from JSON Schema
quicktype --src user-schema.json --src-lang schema --lang ts --just-types
This gives you the most accurate output because a JSON Schema explicitly marks required vs. optional fields, enums, and formats.
When to use it: Large or complex payloads, projects where the API provides a JSON Schema, or when you want the highest quality generated output.
Pros:
- Infers optional fields and unions from multiple samples
- Supports JSON Schema and GraphQL as input
- Highly configurable (naming style, export style, readonly)
- Active maintenance and broad language support
Cons:
- Heavier dependency than json-ts
- Default output includes runtime converters unless you pass
--just-types - Overkill for simple, small JSON structures
4. Online Converters
Sometimes you just want to paste JSON into a browser and grab the TypeScript output. No npm install, no CLI flags, no project setup.
DevToolBox's JSON to TypeScript converter does exactly that. You paste your JSON on the left, and the tool generates clean TypeScript interfaces on the right. It handles nested objects, arrays, null values, and optional fields.
This kind of tool is ideal during the exploratory phase of development. You hit an API endpoint with curl, copy the response, paste it into the converter, and have working interfaces in seconds. From there you can refine the types manually if needed.
Other online converters exist (json2ts.com, transform.tools), but I find having one integrated into a broader developer toolbox more convenient since you often need related conversions in the same session.
When to use it: Quick exploration, prototyping, onboarding onto a new API, or when you are on a machine where you cannot install global npm packages.
Pros:
- Zero setup, works in any browser
- Instant visual feedback
- Good for sharing with teammates who are not CLI-oriented
Cons:
- Not automatable in CI/CD pipelines
- You must paste the JSON manually each time
- Output quality varies across different online tools
5. Runtime Validation with Zod
The four approaches above all share a fundamental limitation: they produce static types that vanish at runtime. If an API returns unexpected data, TypeScript will not catch it. Zod solves this by giving you a schema that validates data at runtime and infers TypeScript types from the same definition.
Defining a Zod schema
import { z } from "zod";
const ProjectSchema = z.object({
projectId: z.string(),
title: z.string(),
status: z.enum(["active", "archived", "draft"]),
tags: z.array(z.string()),
});
const UserMetaSchema = z.object({
lastLogin: z.string().datetime(),
twoFactorEnabled: z.boolean(),
});
const UserSchema = z.object({
id: z.number().int(),
name: z.string(),
email: z.string().email(),
role: z.enum(["admin", "editor", "viewer"]),
projects: z.array(ProjectSchema),
meta: UserMetaSchema,
});
// Derive the static TypeScript type
type User = z.infer<typeof UserSchema>;
Now User is a proper TypeScript type with full autocompletion, and UserSchema.parse(data) validates incoming JSON at runtime. If the data does not match, Zod throws a structured error telling you exactly which field failed and why.
Parsing API responses
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
const json = await res.json();
return UserSchema.parse(json); // throws if invalid
}
If you prefer not to throw, use .safeParse():
const result = UserSchema.safeParse(json);
if (result.success) {
console.log(result.data.name); // fully typed
} else {
console.error(result.error.issues);
}
Generating Zod schemas from JSON
Writing Zod schemas by hand for large payloads can be tedious. The JSON to Zod converter on DevToolBox generates a complete Zod schema from any JSON input. Paste your API response, get a ready-to-use schema with z.infer type export included. This is particularly useful when onboarding onto a new API with large response objects.
When to use it: Any project where data crosses a trust boundary: API responses, form submissions, webhook payloads, environment variables, or data from third-party services.
Pros:
- Single source of truth for types and validation
- Structured error messages pinpoint exactly what went wrong
- Composable: schemas can extend, merge, pick, and omit fields
- Works with popular frameworks (Next.js, tRPC, React Hook Form)
Cons:
- Adds a runtime dependency (~50KB minified)
- Learning curve for advanced features (discriminated unions, transforms, pipes)
- Schema code is more verbose than a plain interface
Comparison Table
| Approach | Setup | Automation | Runtime Safety | Best For |
|---|---|---|---|---|
| Manual typing | None | No | No | Small payloads, precise types |
| json2ts CLI | npm install | Yes | No | CI pipelines, medium payloads |
| quicktype | npm install | Yes | No | Large payloads, JSON Schema |
| Online converter | None | No | No | Quick exploration, prototyping |
| Zod | npm install | Partial | Yes | API boundaries, form validation |
Which Should You Choose?
For a quick prototype or a small internal tool, manual typing or an online converter gets the job done in under a minute. For a production codebase with multiple API endpoints, quicktype paired with your JSON Schema gives you the most reliable generated types. And for anything where bad data can cause real damage -- payment processing, user authentication, webhook handlers -- Zod is worth the extra weight because it catches problems that static types simply cannot.
In practice, many teams combine approaches. They use quicktype or an online tool to generate the initial interfaces, then wrap critical API boundaries with Zod schemas for runtime safety. That layered strategy gives you both developer ergonomics and production resilience.
Whatever path you take, the key point remains: JSON without types is a bug waiting to happen. Pick the approach that fits your workflow and start generating those interfaces today.
Top comments (0)