JSON to TypeScript: How to Generate Types from API Responses
One of TypeScript's biggest advantages is catching type errors at compile time instead of at runtime. But when you fetch data from an API, that data arrives as untyped JSON. Bridging the gap between untyped API responses and your strongly-typed TypeScript code is a critical skill. This guide covers the practical approaches to generating TypeScript types from JSON data.
Why Type Your API Responses?
Without types, you are essentially writing JavaScript with extra steps. Consider this common pattern:
const response = await fetch("/api/users/1");
const user = await response.json(); // type: any
// No autocomplete, no error checking
console.log(user.nmae); // typo goes unnoticedWith proper types, TypeScript catches the typo immediately:
interface User {
id: number;
name: string;
email: string;
createdAt: string;
}
const user: User = await response.json();
console.log(user.nmae); // Error: Property 'nmae' does not existTypes give you autocomplete in your editor, compile-time error detection, self-documenting code, and safer refactoring. The upfront cost of defining types pays for itself quickly.
Manual Typing
For small, stable APIs, writing interfaces by hand is perfectly fine. Read the API documentation, look at a sample response, and write the matching TypeScript interface. This gives you the most control over naming, optionality, and documentation.
The downside is that manual types can drift out of sync with the actual API. If the backend adds a field or changes a type, your TypeScript interface will not know about it until something breaks at runtime.
Automated Generation from JSON
When you have a JSON sample but no schema definition, you can generate TypeScript interfaces automatically. The process works by analyzing the JSON structure: objects become interfaces, arrays become typed arrays, and primitive values map to their TypeScript equivalents.
For example, given this JSON:
{
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"roles": ["admin", "editor"],
"profile": {
"bio": "Developer",
"avatar": null
}
}An automated tool would generate:
interface Root {
id: number;
name: string;
email: string;
roles: string[];
profile: Profile;
}
interface Profile {
bio: string;
avatar: string | null;
}Handling Edge Cases
Automated generation works well for most cases, but there are a few situations that need manual attention:
- Optional fields. A single JSON sample cannot tell you which fields are optional. If a field is missing from the sample, the generator will not include it at all. If it is present, it will be marked as required. You may need to add
?manually for fields that are not always present. - Union types. If a field can be a string or a number depending on context, a single sample will only show one type. Provide multiple samples or adjust the types manually.
- Enums and literals. A generator will type a status field as
string, but you might want"active" | "inactive" | "pending". - Date strings. JSON has no date type, so dates come as strings. The generator will type them as
string, but you may want to add a comment or use a branded type.
Schema-First Approaches
For production applications, the best approach is generating types from a schema rather than from sample data:
- OpenAPI/Swagger: Tools like
openapi-typescriptgenerate types directly from your API specification. This is the gold standard because the schema is the single source of truth. - GraphQL: Tools like
graphql-codegengenerate TypeScript types from your GraphQL schema and queries. - JSON Schema: Tools like
json-schema-to-typescriptconvert JSON Schema definitions to TypeScript interfaces. - Zod / Valibot: Runtime validation libraries that let you define schemas in TypeScript and infer types from them. This gives you both compile-time types and runtime validation.
Best Practices
- Never use
anyfor API responses. At minimum, useunknownand narrow the type with validation. - Keep types close to where they are used. Co-locate API types with the API client code that uses them.
- Use
interfacefor object shapes. Interfaces are generally preferred over type aliases for object shapes because they are extensible and produce better error messages. - Validate at the boundary. TypeScript types disappear at runtime. For critical data, validate the response shape with a runtime library like Zod before trusting it.
- Automate type generation in CI. If you use a schema-first approach, add type generation to your build pipeline so types stay in sync automatically.
Try it yourself
Paste any JSON and instantly get TypeScript interfaces. Handles nested objects, arrays, and null values. Runs entirely in your browser.
Open JSON to TypeScript Converter