Skip to content
jsonbeautifiers
English

JSON to TypeScript

Interfaces merged across every array element, so optional fields are actually optional.

JSON
TypeScript

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.