Paste a JSON response into the input, name the root type, and choose interfaces or type aliases. Inspect the generated declarations before copying or downloading them. The small example below shows the actual output with Root as the name and interfaces selected. Review nested objects and arrays against several real responses instead of treating one sample as a complete API contract.
Distinguish optional properties from nullable values when refining the result. Add literal unions, readonly fields, branded identifiers, or discriminated unions manually where your contract requires them. Enable strictNullChecks and validate external data at runtime; a type assertion does not check a response. If your API publishes an OpenAPI contract, consider generating from that specification with openapi-typescript rather than guessing requirements from sample values. Additional fenced examples illustrate manual contract refinements.
{"name":"Ada"}
export interface Root {
name: string;
}
Manual integration options: Zod / io-ts / Valibot runtime schemas; typed fetch, Axios, TanStack Query.
Keep shared API types together. Interface extension and type unions serve different modeling needs.
{
"id": 42,
"username": "jdoe",
"email": "[email protected]",
"isActive": true
}
interface User {
id: number;
username: string;
email: string;
isActive: boolean;
}
{
"id": 42,
"username": "jdoe",
"address": {
"street": "123 Main St",
"city": "Springfield",
"zipCode": "62704",
"country": "US"
}
}
interface Address {
street: string;
city: string;
zipCode: string;
country: string;
}
interface User {
id: number;
username: string;
address: Address;
}
{
"id": 42,
"username": "jdoe",
"roles": ["admin", "editor"],
"posts": [
{ "id": 1, "title": "Hello World" },
{ "id": 2, "title": "TypeScript Tips" }
]
}
interface Post {
id: number;
title: string;
}
interface User {
id: number;
username: string;
roles: string[];
posts: Post[];
}
{
"id": 42,
"username": "jdoe",
"bio": "Software developer"
}
interface User {
id: number;
username: string;
bio?: string; // May be absent from the response
}
{
"id": 42,
"username": "jdoe",
"avatarUrl": null,
"deletedAt": null
}
interface User {
id: number;
username: string;
avatarUrl: string | null;
deletedAt: string | null; // ISO date string or null
}
interface User {
id: number;
username: string;
middleName?: string | null; // May be missing, or present but null
}
{
"results": [
{ "type": "user", "id": 1, "username": "jdoe" },
{ "type": "post", "id": 2, "title": "Hello" }
]
}
interface UserResult {
type: 'user';
id: number;
username: string;
}
interface PostResult {
type: 'post';
id: number;
title: string;
}
type SearchResult = UserResult | PostResult;
interface SearchResponse {
results: SearchResult[];
}
function renderResult(result: SearchResult) {
if (result.type === 'user') {
console.log(result.username); // TypeScript knows this is UserResult
} else {
console.log(result.title); // TypeScript knows this is PostResult
}
}
{ "status": "active" }
type UserStatus = 'active' | 'inactive' | 'suspended';
interface User {
id: number;
status: UserStatus;
}
interface BaseEntity {
id: number;
createdAt: string;
updatedAt: string;
}
interface User extends BaseEntity {
username: string;
email: string;
}
type ApiResponse<T> = {
data: T;
meta: { total: number; page: number };
};
type UserOrAdmin = User | Admin;
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
username: z.string(),
email: z.string().email(),
isActive: z.boolean(),
bio: z.string().nullable().optional(),
});
type User = z.infer<typeof UserSchema>;
// Now validate at runtime
const response = await fetch('/api/users/42');
const data = await response.json();
const user = UserSchema.parse(data); // Throws if shape doesn't match
async function fetchJson<T>(url: string, schema?: z.ZodType<T>): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (schema) {
return schema.parse(data);
}
return data as T;
}
// Usage
const user = await fetchJson<User>('/api/users/42');
const validatedUser = await fetchJson('/api/users/42', UserSchema);
import axios from 'axios';
interface PaginatedResponse<T> {
data: T[];
meta: {
total: number;
page: number;
perPage: number;
lastPage: number;
};
}
async function getUsers(page: number = 1) {
const response = await axios.get<PaginatedResponse<User>>('/api/users', {
params: { page },
});
return response.data; // Fully typed
}
import { useQuery } from '@tanstack/react-query';
function useUser(id: number) {
return useQuery({
queryKey: ['user', id],
queryFn: () => fetchJson<User>(`/api/users/${id}`),
});
}
// In your component
const { data: user, isLoading } = useUser(42);
// user is typed as User | undefined
{
"compilerOptions": {
"strict": true
}
}
interface User {
readonly id: number;
readonly username: string;
readonly email: string;
readonly createdAt: string;
}
type ImmutableUser = Readonly<User>;
type UserId = number & { readonly __brand: 'UserId' };
type PostId = number & { readonly __brand: 'PostId' };
function getUser(id: UserId): Promise<User> { /* ... */ }
function getPost(id: PostId): Promise<Post> { /* ... */ }
const userId = 42 as UserId;
const postId = 7 as PostId;
getUser(userId); // OK
getUser(postId); // Type error! Can't pass PostId where UserId is expected
src/
types/
api/
user.ts
post.ts
common.ts // Shared types like PaginatedResponse
index.ts // Re-exports
// Don't do this
const user = (await response.json()) as User; // Trusts blindly
// Do this
const data = await response.json();
const user = UserSchema.parse(data); // Validates shape
npx openapi-typescript https://api.example.com/openapi.json -o ./src/types/api.ts
Processing runs in your browser. Choosing Share uploads the selected tool state; anyone with the link can open it. Signing in to save sends content to your account library and records save history. Review the selection before either action. Closing the page does not delete those copies. Copying locally does not create a server share.
Nested objects produce separate declarations referenced by their parent. Arrays can combine sampled object shapes with optional fields where some elements omit a key. Review the generated names and unions against your contract. A missing variant cannot be inferred from data you did not supply, and recursive domain relationships require manual modeling.
Copy the declarations or download a .ts file, then review and type-check them in your project. They are an inferred first draft rather than proof of API correctness. Keep shared types together, enable strict null checks, and use interface extension or type aliases according to the relationships your application needs to express.
No. TypeScript declarations disappear at runtime, and an assertion only tells the compiler to trust a value. Validate external responses at the boundary with a schema library such as Zod when shape guarantees matter. Keep the runtime schema and the inferred or manually refined type aligned as the API changes.
Only values present in the sample can inform inference. Array object samples can reveal missing keys and unions, but unseen variants remain unknown. Compare multiple responses and the API contract. Review nullable fields, literal unions, and optional properties manually, or generate from OpenAPI when the specification defines the complete contract.