JSON to TypeScript
Interfaces merged across every array element, so optional fields are actually optional.
Nothing you paste leaves your browser. The connect-src allowlist makes that a browser guarantee rather than a promise. Check it yourself
Turn a JSON payload into TypeScript interfaces. Nested objects become their own named types, arrays are merged across every element, and structurally identical shapes are emitted once and reused.
Inference from a sample is guesswork. The point of this page is that the guesses are visible.
Optional fields, which is where most generators fail
A generator that reads element zero of an array and stops produces an interface where every field is required. The first record that omits an optional field then fails to type-check, and you edit the generated types by hand, which defeats the point.
This one merges every element. A key present in all of them is required; a key present in some is marked optional with a question mark. That is real information, extracted from your sample, that a single-element generator throws away.
The other decisions
- null becomes a union
- A field seen as both a string and null is string | null, not any and not string.
- An empty array is unknown[]
- Not any[]. An empty array carries no type information, and any[] would quietly switch off checking for everything that touches it.
- Repeated shapes are deduplicated
- A list of five hundred records produces one interface, not five hundred.
- Array element names are singularised
- A categories array produces a Category interface.
- Invalid identifiers are quoted
- A key of has-dash or 2fast or class becomes a quoted property name.
- Unsafe integers are flagged
- A field typed number when the sample held an integer above 2^53-1 is a lie, because JavaScript cannot represent it. The note says so; the correct fix is upstream, sending it as a string.
What a generated type does not give you
A TypeScript interface is erased at runtime. It tells the compiler what you expect and does nothing at all when the API sends something else. For that you need a runtime validator, and the honest workflow for an external API is a schema that validates and infers the type from the same definition.
Zod, Valibot and ArkType all do this. Generate an interface here to understand a payload, then write the runtime schema for the boundary you do not control.
How to do this in code
The same idea in code, and what to use at a boundary you do not trust.
sh quicktype
quicktype supports many target languages. Passing several samples is the flag that matters.
npx quicktype --lang ts --just-types --src-lang json payload.json
# Several samples, which is what makes optionality accurate
npx quicktype --lang ts --just-types samples/*.json ts Zod
This is the pattern to reach for at an API boundary. The interface tells the compiler; the schema tells the truth.
import { z } from 'zod';
const User = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
verifiedAt: z.string().nullable(),
roles: z.array(z.string()),
});
// One definition, both a runtime check and a static type
type User = z.infer<typeof User>;
const result = User.safeParse(await res.json());
if (!result.success) console.error(result.error.issues); ts Type-only, from a literal
// If the data is a constant you control, TypeScript can infer
// the type without a generator at all.
const config = {
retries: 3,
endpoints: ['a', 'b'],
} as const;
type Config = typeof config; Questions
- Why is a field optional when my API always sends it?
- Because at least one record in the sample did not have it. That is either a real optional field or an incomplete sample. Paste an array with more records and the answer gets better.
- Should I use interface or type?
- For object shapes it barely matters. Interfaces support declaration merging and produce marginally nicer error messages; type aliases can express unions and mapped types. Both are available above.
- Do these types validate anything at runtime?
- No. TypeScript types are erased when the code compiles. They describe what you believe; they do not check it. For data crossing a network boundary, use a runtime validator.