Home JSON to TypeScript

JSON to TypeScript interface generator

Paste a JSON array and get the interfaces that describe it. Nesting is kept, keys that are sometimes absent are marked optional, and it all runs in this tab.

Why it reads every record, not the first one

The usual failure of a JSON-to-type tool is that it looks at one object. The first record's null becomes the type null; a key that happens to be absent from it never appears at all; a field that is 7 in the first row and 7.5 in the fortieth is typed as an integer. Each of those compiles, passes review and breaks on the second page of results.

This reads every record you paste and merges what it finds:

  • A key missing from any record is optional — the marker is on the key, not the value.
  • A key that is sometimes null is nullable — a different statement from optional, and both can be true.
  • Mixed numbers collapse to one number type. A field holding 7 and 9.5 is a number, not "an integer or a number".
  • Recognised string shapes keep their meaning where the target can express it — UUIDs, ISO timestamps, dates, emails and URLs.
  • An empty array says nothing about its elements, so it stays unknown rather than being guessed from a sibling record.

So paste more than one record. Paste the awkward ones — the row with the null, the row missing the optional field, the one from the second page. The output is only as good as the range of the sample, and that is a property of the input, not of the tool.

Nesting becomes named interfaces

A nested object gets its own interface rather than an inline literal. Inline is technically the same type and unreadable past two levels, and an unreadable type is one nobody edits — so the generated code stops being the source of truth the first time the shape changes.

export interface User2 {
  name: string;
  email: string;
  nickname?: string;
}

export interface User {
  id: number;
  user: User2;
  tags: string[];
  score: number;
  created: string;
}

Names come from the key, so a property called user produces a User-ish name; when that collides with the root name it is numbered rather than silently merged. Rename them — the output is a starting point, not a lockfile.

Optional and nullable are different

nickname?: string means the key may be absent. nickname: string | null means the key is there and its value may be null. TypeScript treats these differently under exactOptionalPropertyTypes, and an API that returns explicit nulls is not the same as one that omits the field. Both are inferred separately, and a key that is both sometimes-absent and sometimes-null gets both.

What it does not infer

  • Literal unions. A status field holding only "open" and "closed" is typed string, not 'open' | 'closed' — a sample cannot tell a closed set from an open one, and guessing wrong produces a type that rejects valid data.
  • Branded or nominal types. A UUID is string; TypeScript has no built-in for it.
  • Dates. An ISO timestamp is string, because that is what JSON.parse gives you. Convert deliberately rather than typing it as Date and being wrong at runtime.
  • Recursive shapes. A tree that nests into itself produces nested interfaces rather than a recursive one.

Common questions

Does it read the whole array or just the first object?

The whole array. That is the difference that matters: inferring from the first object alone marks nothing optional, types a first-row null as null, and calls a field an integer because the first value happened to be whole.

How are nested objects handled?

Each gets its own named interface rather than being inlined. Inline object literals are the same type and become unreadable past two levels, and generated code nobody can read stops getting maintained.

What is the difference between the ? and | null in the output?

The question mark means the key may be absent from the object. The union with null means the key is present and its value may be null. They are different things, TypeScript treats them differently, and both are inferred separately.

Why is my status field string rather than a union of its values?

Because a sample cannot distinguish a closed set from an open one. If your data only ever contains "open" and "closed", that may be a coincidence of the sample — and a literal union that is wrong rejects valid data at compile time.

Is my JSON uploaded anywhere?

No. Inference and generation run in your browser, so the JSON never leaves your machine. That matters here more than on most converters, because the JSON people want typed is usually a real API response.

Last updated