If you've ever pasted a single JSON object into a "json to typescript" tool and gotten back a clean interface, only to have your API return a slightly different shape the next day and blow up your build — you've hit the single-sample problem. It's the most common, least talked-about failure mode in JSON-to-TypeScript conversion, and it's worth understanding before you trust any generated type in a real codebase.
What "generate TypeScript types from JSON" actually means
At its core, converting JSON to TypeScript is a structural mapping exercise. JSON has a small set of value types — string, number, boolean, null, array, object — and each one has an obvious TypeScript counterpart:
{"id":42,"name":"Ada Lovelace","active":true}
export interface Root {
id: number;
name: string;
active: boolean;
}
For a single, simple object, that's the whole job. The complexity — and the part where converters genuinely differ in quality — shows up the moment your JSON sample is an array of objects, which is the shape almost every real REST API response actually has.
The single-sample problem: why optional fields go missing
Here's the trap. Say your API returns a list of users, and one of them is missing an email field because they signed up via SSO:
[{"id":1,"name":"Ada","email":"ada@example.com"},{"id":2,"name":"Alan"}]
A converter that only looks at the first array element will produce:
export interface Root {
id: number;
name: string;
email: string;
}
That's wrong — email is not guaranteed to be present, but the generated type says it is. The bug won't show up immediately. It shows up later, as a runtime undefined where TypeScript swore a string would be, because nothing forced the compiler to check the field that was actually missing in your sample data.
This is the single biggest correctness gap across the free "json to typescript interface generator" tools you'll find in a search: most infer structure from element 0 of an array (or the one object you pasted) and never look at the rest.
What correct multi-sample merging looks like
The fix is straightforward in concept, if fiddly to implement correctly: when the JSON root is an array of similar objects, merge the union of every key seen across every element — not just the first — into one interface.
export interface Root {
id: number;
name: string;
email?: string; // absent from at least one sample
}
The same logic applies to types, not just presence. If a field is a number in one record and a string in another — a common pattern when an API is mid-migration, or a field is sometimes an ID and sometimes a slug — proper merging produces a union:
[{"value":42},{"value":"forty-two"}]
export interface Root {
value: number | string;
}
A converter that samples only the first element would type value as number and quietly break the moment it saw a string. This matters just as much for nested arrays of objects: if a tags array inside one record has an item missing a weight field, that optionality has to survive being merged again when the outer records themselves get merged together — a subtlety that's easy to get right for a single level of nesting and easy to lose once you go two levels deep.
Handling JSON to TypeScript nested objects
Nested objects raise a second, separate design question: should the nested shape be written inline, or pulled out into its own named interface?
{"id":1,"address":{"city":"London","postcode":"SW1A 1AA"}}
Inline keeps everything in one block:
export interface Root {
id: number;
address: { city: string; postcode: string };
}
Extracted gives the nested shape its own name, which is usually more readable and lets you reuse the type elsewhere:
export interface Address {
city: string;
postcode: string;
}
export interface Root {
id: number;
address: Address;
}
Extraction is generally the better default for anything beyond a trivial one-off shape, especially if the same nested structure appears under more than one property (a billingAddress and shippingAddress with identical fields, for instance) — a good tool should deduplicate those into a single interface rather than generating two identical ones with different names.
Other details worth checking before you trust a converter's output
A handful of smaller rules separate a converter that produces compiling, correct TypeScript from one that produces something that merely looks right:
-
Empty arrays should infer as
unknown[], not silentlyany[]— an empty array genuinely tells you nothing about its element type, andanydefeats the entire point of generating types in the first place. -
Keys that aren't valid TypeScript identifiers — hyphens, spaces, a leading digit, reserved words — need automatic quoting (
"last-login": string;), or the output won't compile at all. -
nullvalues should be handled explicitly, either as an honestT | nullunion or folded intokey?: Tdepending on your team's convention — never silently dropped. -
exportandreadonlyshould be simple toggles you control, not a hard-coded style choice you have to find-and-replace after the fact.
Try it: a json to typescript converter built around correct merging
Skojio's JSON to TypeScript Converter exists specifically to close the single-sample gap described above. Paste a JSON object or an array of objects and it will:
- Merge the union of keys across every array element (not just the first) to correctly infer optional fields and unions
- Recursively apply the same merging to nested arrays of objects, so optionality survives being merged at multiple levels
- Let you toggle
interfacevstype, extract-named-interfaces vs inline nesting,export, andreadonly - Automatically quote invalid-identifier keys, with a visible note when it happens
- Give you a copy button and a one-click
.tsdownload
Everything runs in your browser — nothing you paste is sent anywhere. If you want to check your JSON's shape is well-formed before generating types from it, run it through JSON Formatter & Validator first; if you'd rather get the same data into tabular CSV instead of TypeScript, JSON ↔ CSV Converter is the same-input, different-output sibling tool.
- Most free JSON-to-TypeScript converters infer structure from a single sample (often just array element 0), which silently mis-types fields that are sometimes missing or sometimes a different type.
- Correct multi-sample merging unions the keys across every array element: absent-in-some-records becomes
key?: T, differently-typed-across-records becomes a union type. - Nested arrays of objects need the same merge logic applied recursively — optionality discovered one level down has to survive being merged again at the level above.
- Extracting nested objects into named, deduplicated interfaces is usually more useful than writing them inline, especially when the same shape appears under more than one property.
- Empty arrays should infer as
unknown[], notany[]; invalid-identifier keys need automatic quoting, not a compile error.