Why generated types are a starting point, not the truth
This tool reads one JSON sample and produces matching TypeScript interfaces — a huge time-saver over hand-typing a deeply nested API response. But it’s inferring structure from a single example, and that’s the root of every limitation worth knowing:
- Optional fields look required (covered above).
- Mixed or sparse arrays infer from the first element, so an array where some objects have extra fields produces a type missing those fields.
- Nullable values can only be guessed — a
nullin the sample tells the generator the key exists but not its real type, so it produces something likeunknown | nullfor you to refine.
The output gets you 90% of the way; the last 10% is applying knowledge of the API that no single sample contains.
Interface vs type — which to pick
The generator offers both outputs. The practical guidance:
Use interface | Use type |
|---|---|
| Object shapes (most API responses) | Unions, intersections, tuples |
| You might extend/merge it later | Mapped or conditional types |
| Public API contracts | One-off compositions |
For typing API responses — the dominant use case — interface is the conventional choice. Reach for type aliases when you need union types or other compositions interfaces can’t express.
Why typing your API layer pays off
Untyped JSON is the source of the most common runtime error in JavaScript: Cannot read property of undefined. Generating interfaces from real responses gives you editor autocomplete, compile-time checks, and — crucially — refactoring safety: when the API changes a field, TypeScript flags every line that touches it, turning a silent production bug into a build error you fix in minutes. Generate the types once, drop them into your API client layer, and let the compiler watch your back.
A pragmatic workflow
- Generate interfaces from a representative response (ideally one with all optional fields present).
- Refine — add
?to optional properties, tightenunknown | nullto the real type, name the root meaningfully. - Validate at the boundary — for data crossing into your app from the network, pair the type with a runtime schema (Zod et al.) so the compile-time promise is actually enforced when it matters.