Writing TypeScript types by hand for a JSON response you already have is repetitive and easy to get subtly wrong on the first pass, especially once a field is optional or a value is nested three levels deep.
Using the JSON to TypeScript generator
Paste a document, or several records in an array, name the root type, choose interfaces or type aliases, and press Generate types. The type inference is shared with the schema generator on this site, so the two pages agree about what your document actually contains and differ only in how they write the answer down.
Given
[
{"user": {"name": "a"}, "tags": [], "meta": {}},
{"user": {"name": "b"}, "tags": ["x"], "meta": {}, "extra": true}
]
the output is:
export interface RootItem {
user: User
tags: string[]
meta: Record<string, unknown>
extra?: boolean
}
export interface User {
name: string
}
export type Root = RootItem[]
Why nested objects get their own named type
An inline type three levels deep is unreadable, and nothing else in the
file can refer to it. Names come from the key that holds the object,
singularised inside an array (RootItem from Root), and a repeated name
gets a number appended so the output always compiles without a collision.
What the optional mark actually promises
extra?: boolean means the key was missing from at least one record in
your sample, exactly the same rule the schema generator uses for
required. It does not mean the field is guaranteed optional across your
whole API, only that your sample cannot prove it is always present. Read
the mark as evidence from your data, not as a guarantee from the source
system.
Empty values
An empty object becomes Record<string, unknown> rather than an empty
interface, since an empty interface accepts anything and says nothing
useful about the shape. An empty array becomes unknown[], because there
is nothing inside it to infer a element type from, and inventing one would
be a guess dressed up as a fact.
Why every number is just number
TypeScript has no separate integer type, so a field that is always a whole
number in your sample is still typed number, the same as one that
contains decimals. That mirrors how TypeScript itself models numbers, so
the generated types compile and behave exactly like hand-written ones.