JSON to TypeScript: 4 Approaches Compared (2026)
Every TypeScript developer who consumes APIs eventually asks the same question: how do I get types for this JSON? There are four real answers in 2026, each with different trade-offs in accuracy, maintenance cost, and runtime safety. Here's how they compare — with working code for each.
The Example JSON
We'll use the same JSON response across all four approaches so you can see exactly what each produces:
{
"user": {
"id": 42,
"name": "Jane Doe",
"email": "jane@example.com",
"roles": ["admin", "editor"],
"preferences": {
"theme": "dark",
"notifications": true
},
"createdAt": "2024-01-15T09:30:00Z"
}
}This covers the common patterns: nested objects, arrays, booleans, numbers, and ISO date strings — everything you encounter in a real API response.
Approach 1: Manual Typing
Write the interfaces yourself, guided by the JSON structure. Time-consuming for large responses, but gives you full control over naming and documentation.
interface UserPreferences {
theme: string
notifications: boolean
}
interface User {
id: number
name: string
email: string
roles: string[]
preferences: UserPreferences
createdAt: string // ISO 8601 date string
}
interface ApiResponse {
user: User
}Pros
- Full naming control
- Add JSDoc comments
- No dependencies
- Handle optional fields explicitly
Cons
- ✗ Tedious for large responses (50+ fields)
- ✗ No runtime validation
- ✗ Drift when API changes
- ✗ Easy to miss optional fields
Best for: small, stable APIs where you want total control and the JSON has 10 fields or fewer.
Approach 2: quicktype (CLI + Online)
quicktype.io is the most widely used JSON-to-types tool in 2026. It infers TypeScript interfaces, Zod schemas, io-ts codecs, and types for 20+ languages from sample JSON. The npm package (quicktype v23.2.6) is the same engine as the website.
Generated output for our example JSON:
// Generated by quicktype (quicktype.io)
export interface Root {
user: User
}
export interface User {
id: number
name: string
email: string
roles: string[]
preferences: Preferences
createdAt: Date
}
export interface Preferences {
theme: string
notifications: boolean
}CLI usage:
# Install quicktype globally npm install -g quicktype # Generate TypeScript interfaces from a JSON file quicktype --lang ts --src response.json --out types.ts # Generate from a URL (quicktype fetches the JSON) quicktype --lang ts --src-lang json \ https://api.example.com/user/42 --out types.ts # Generate with Zod schemas instead of plain interfaces quicktype --lang ts --src response.json \ --framework zod --out types.ts
createdAt as Date type based on the ISO 8601 string pattern. It also generates a converter function that calls new Date() during deserialization. Useful, but adds a runtime dependency you may not want.Pros
- Handles complex nested structures automatically
- 20+ output languages
- CLI integrates into build pipelines
- Can merge multiple sample inputs for better inference
Cons
- ✗ Infers from samples — optional fields need multiple samples
- ✗ Generated code is verbose
- ✗ quicktype-core npm package is 6MB
- ✗ Still no runtime validation in plain interface mode
Approach 3: Zod Schema-First
Zod (34K+ GitHub stars as of April 2026) inverts the problem: define a schema, derive the TypeScript type from it. This is the approach used in most production TypeScript codebases in 2026 because it gives you runtime validation for free.
import { z } from 'zod'
const PreferencesSchema = z.object({
theme: z.string(),
notifications: z.boolean(),
})
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.email(),
roles: z.array(z.string()),
preferences: PreferencesSchema,
createdAt: z.string().datetime(),
})
// TypeScript type inferred from schema
type User = z.infer<typeof UserSchema>
// Validate at runtime
const result = UserSchema.safeParse(apiResponse)
if (result.success) {
// result.data is fully typed as User
}The key insight: z.infer<typeof UserSchema> produces the TypeScript type automatically. You write the schema once and get both validation and types from it.
Pros
- Runtime validation — catches API changes at the boundary
- Types derived from schema, always in sync
- Excellent error messages
- 8KB gzip — production-viable
Cons
- ✗ You still have to write the schema manually
- ✗ Performance overhead on large arrays
- ✗ Adds a dependency
Combination approach: Use quicktype to generate a starter Zod schema (--framework zod), then refine it manually. Best of both worlds.
Approach 4: ts-toolbelt DeepRequired
ts-toolbelt (6.5K GitHub stars) provides utility types for deep transformation of existing types. If you already have a base interface but need to ensure all nested fields are required or optional, ts-toolbelt's O.Required and O.Optional handle it at the type level.
import type { O } from 'ts-toolbelt'
// Make all nested fields required
type StrictUser = O.Required<User, PropertyKey, 'deep'>
// Make all nested fields optional
type PartialUser = O.Optional<User, PropertyKey, 'deep'>
// Pick specific nested fields
type UserSummary = O.Pick<User, 'name' | 'email'>ts-toolbelt isn't a JSON-to-types tool per se — it's a type transformation library. Its main value here is refining generated or manually-written interfaces, not replacing them.
Comparison Table
| Approach | Time to Types | Runtime Validation | Handles Optional Fields | Dependencies |
|---|---|---|---|---|
| Manual typing | 10-30 min | No | Manual | None |
| quicktype | 30 seconds | No (or with Zod flag) | Inferred from samples | quicktype-core (6MB) |
| Zod schema | 5-15 min | Yes | Explicit z.optional() | zod (8KB gzip) |
| ts-toolbelt | N/A (modifier only) | No | Via O.Optional | ts-toolbelt (types only) |
Which One Should You Use?
Quick prototype or one-off script
Use quicktype. Paste JSON, get types in 30 seconds, move on.
Production app consuming external APIs
Zod schema-first. External APIs change without notice. Runtime validation at the API boundary catches breaking changes before they propagate through your app.
Large API with 50+ field response
quicktype to generate the starter Zod schema (--framework zod), then manually mark optional fields and add business-logic refinements.
Internal API you control
Share types from source rather than generating them. A shared types package or tRPC eliminates the JSON-to-types problem at the root.
Explore JSON in the Browser
Before generating types, use our JSON formatter to explore your API response structure, format it for readability, and validate the syntax.
Browser-only — your API response data stays local. TypeScript type generation is on our roadmap.
Open JSON Formatter