Turn a JSON response into TypeScript interfaces. Every nested object gets its own named interface, and a key that is missing from some elements of an array comes out optional instead of required.
{
"id": 1,
"name": "Ana",
"orders": [
{ "order_id": 1001, "note": "gift" },
{ "order_id": 1002 }
]
} export interface Order {
order_id: number;
note?: string;
}
export interface User {
id: number;
name: string;
orders: Order[];
} Arrays are scanned in full, not sampled. A key present in some elements and absent from others becomes optional, which usually matches an API that leaves out null fields.
JSON has one number type. A field that is 10 in one record and 12.5 in another comes out as number, since TypeScript has no separate integer type either.
A nested object becomes its own exported interface instead of an inline shape three levels deep, so you can import it elsewhere and use it in a function signature.
A field that is null in every sample has nothing to infer from and comes out as null. Use a sample with a real value there, or write that one type by hand.
An interface checks nothing at runtime. TypeScript erases it at compile time, so if the API changes shape, the code still compiles and then throws three functions away from the cause.
The fix is to validate at the boundary and derive the type from the validator, not the other way round. With zod you write the schema once and get the type from z.infer; with a JSON Schema you can generate both. Use this page to see the shape quickly, then decide whether the data crossing that boundary needs a runtime check.
// npm i zod
import { z } from "zod";
const Order = z.object({
order_id: z.number(),
total: z.number(),
note: z.string().optional(),
});
const User = z.object({
id: z.number(),
name: z.string(),
orders: z.array(Order),
});
// One source of truth: the schema checks at runtime, the type comes from it
type User = z.infer<typeof User>;
const res = await fetch("/api/user/1");
const user = User.parse(await res.json()); // throws if the API changed shape We recommend this pattern. A generated interface tells the compiler what you expect to arrive; a schema checks what arrived.
# From a JSON sample
npx quicktype --lang ts --src-lang json sample.json -o types.ts
# From a JSON Schema, which carries more information
npx json-schema-to-typescript schema.json -o types.d.ts
# From an OpenAPI spec, if the API publishes one
npx openapi-typescript https://api.example.com/openapi.json -o api.d.ts
# The last one is the best option when it is available: the types stay
# in step with the API because they are generated from its own contract. Interfaces and type aliases are close to interchangeable for a shape derived from JSON. Interfaces can be reopened and merged across declarations, which matters when you are augmenting a library's types; type aliases can express unions and mapped types, which interfaces cannot. For plain data, use whichever your codebase already uses.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
Each one becomes its own named interface instead of an inline type. A declaration nested three levels deep is valid but hard to read, and a named interface is something you can export and reuse.
The tool looks at every element, not just the first, and merges the keys it finds. A field missing from some elements comes out optional, which usually matches an API that leaves out null fields.
It comes out as unknown rather than any, for two reasons. An empty array in every sample gives unknown[], and elements that disagree on the type, such as a string in one and a number in another, give unknown. A field that is null in every sample comes out as null. Widen the sample or write that type by hand.
Either works. Interfaces can be extended and merged across declarations, and type aliases can express unions. For a shape derived from JSON the difference rarely matters, so the tool emits interfaces, and Use type alias switches the output if your codebase prefers aliases.
No. A TypeScript interface is erased at compile time and asserts nothing at runtime, so if the API changes shape, the code still compiles and then throws. Validate at the boundary with zod, io-ts or a JSON Schema check, and derive the type from that.
The same sample produces a Go struct, a Python dataclass or a JSON Schema, and the schema is the one you can validate data against. To tidy the sample first, use the JSON formatter.