What can and cannot be inferred from a sample
Generating types from a JSON sample is inference from a single observation, and it produces a description of that sample rather than of the underlying schema. Everything present becomes required; everything absent does not exist. If the sample happens to omit an optional field, the generated type will not include it, and code written against that type will fail to compile when the field appears.
The reverse error is equally common: a field that is null in the sample but sometimes carries a value is inferred as null, when it should be a nullable union. A field that is an empty array gives no information about its element type at all, and the honest inference is unknown[] rather than a guess.
The practical mitigation is to generate from several representative samples rather than one — ideally including responses with optional fields present and absent — and then to read the result critically. Treat generated types as a first draft that captures the shape, not as a specification.
Optionality, null and undefined
TypeScript distinguishes three things JSON blurs. A property marked ? may be absent from the object. A property typed | null is always present but may hold null. A property typed | undefined is present with an undefined value, which JSON cannot represent at all since there is no undefined literal.
Getting this wrong produces bugs that type checking was supposed to prevent. Marking a field optional when the API always sends it but sometimes sends null means obj.field passes the check and is null at runtime. Under strictNullChecks the distinction is enforced, and without that flag much of TypeScript's value is lost — it should be on.
Arrays of heterogeneous objects are the other structural decision. Inference typically produces a union of the observed shapes, which is accurate but awkward to consume. Where the objects share a discriminating field, converting the union into a discriminated union — a literal type on a common property — lets TypeScript narrow correctly in a switch, which is far more useful than a bare union.
Types are erased, so validate at runtime
The most important thing to understand about generated types is that they provide no runtime guarantee whatsoever. TypeScript types are erased at compile time; nothing checks that the JSON arriving from a network call matches the interface you declared. An API that changes its response shape produces no error at the boundary — instead you get an undefined value several layers deeper, at a point where the cause is no longer obvious.
This is why casting a parsed response with as MyType is a lie to the compiler. It suppresses checking without establishing anything. The response is unknown until something actually inspects it.
The robust pattern is a runtime validation library — Zod, Valibot, io-ts and similar — where you declare a schema once, validate the response against it at the boundary, and derive the static type from that schema. You get one source of truth, a genuine error at the point of failure with a message naming the offending field, and types that cannot drift from the validation. Generated interfaces are a reasonable starting point for writing that schema.