IQ
PayloadIQ
← PayloadIQ Guides

JSON to TypeScript: interfaces that survive real data

A single sample response is enough to generate a type. It usually isn't enough to generate a correct one. Here's how to bridge the gap.

Pasting a JSON response and reading off the field names gets you 80% of a TypeScript interface in a few seconds. The remaining 20% — what is optional, what can be null, what an array actually contains — is where types either protect you or quietly lie to you.

Start from a real response

Take an actual payload, not a hand-written example. Say an endpoint returns:

{
  "id": 1042,
  "name": "Ada Lovelace",
  "email": null,
  "roles": ["admin", "editor"],
  "profile": { "city": "London", "verified": true }
}

A naive mapping gives you something close to:

interface User {
  id: number;
  name: string;
  email: string;        // wrong — this can be null
  roles: string[];
  profile: { city: string; verified: boolean };
}

Null is not optional, and optional is not null

These are two different facts and TypeScript models them differently. A field that is present but can hold null is email: string | null. A field that may be absent from the object is email?: string. Many APIs do both, which is email?: string | null.

One sample can't tell you which is which — it shows you one shape. If you only ever see email: null, you can't know whether the field is sometimes a real string, and if you only see it present you can't know whether it's sometimes omitted. The safe default for fields you're unsure about is to widen to | null and handle it at every read site.

Arrays describe their items, not their length

roles: string[] is fine. The trap is an array of objects where the objects aren't identical — one element has a field the next one doesn't. If element 0 has discount and element 1 doesn't, the correct item type marks it optional: discount?: number. Generating from a single element silently makes every later element look complete.

Numbers that aren't safe numbers

JSON has one number type; JavaScript parses it as a 64-bit float. IDs like 9007199254740993 round when they exceed Number.MAX_SAFE_INTEGER. If your API returns large integer IDs or snowflake-style identifiers, type them as string rather than number, even though the JSON shows a number.

Common mistakes

  • Trusting one response. Generate from several payloads — success, empty, and edge cases — then merge optionals.
  • Forgetting the envelope. If the body is { "data": {...}, "meta": {...} }, type the envelope too, not just data.
  • Treating types as validation. An interface is a compile-time promise. It does nothing at runtime when the API changes — see the Zod guide.
  • Inlining everything. Pull nested objects into named interfaces (Profile) so they're reusable and the errors read clearly.

When to let a tool do it

For anything past a handful of fields, generating by hand is slow and error-prone. Paste the response into the PayloadIQ playground and it infers interfaces with optional and nullable fields detected from the data, names nested objects, and flags large integers. Format the payload first with the JSON Formatter if it arrived minified — readable structure makes the generated types easier to review.

Generated types are a starting point, not gospel. Read them, widen the fields the sample couldn't prove, and commit them next to the code that consumes the endpoint.

Open the PlaygroundJSON Formatter

Related guides

JSON to ZodAPI Response to Typed ClientSchema Quality Check