JSON to TypeScript
Paste a JSON object or array and get back clean TypeScript interfaces, inferred recursively for nested objects and arrays. Handles mixed-type arrays and merges inconsistent object shapes automatically.
Paste any JSON value and set a root interface name. Nested objects become their own named interfaces, arrays of objects merge every observed key into one element type, and primitives map to their TypeScript equivalent.
JSON
TypeScript
Learn more
What JSON to TypeScript conversion is
JSON to TypeScript conversion takes a JSON document — usually a sample response from an API — and generates TypeScript type declarations that describe its structure. Instead of manually writing interface definitions by inspecting raw JSON, you paste a representative sample and get back properly named interfaces, with nested objects broken into their own types and every primitive mapped to its TypeScript equivalent. It's the fastest way to get type-safe code against an API you don't control.
The core value is closing the gap between dynamic JSON and static TypeScript. JSON, by nature, carries no type information — a field might be a string in one response and a number in another, or appear only under certain conditions. TypeScript's job is to catch exactly those mismatches at compile time, but only if the types exist. Generating them from real sample data means the types reflect what the API actually returns, not what the documentation claims it returns, which is often the only trustworthy source of truth.
Type inference: from JSON values to TypeScript types
The inference rules are straightforward at the leaf level: JSON strings map to string, numbers to number, booleans to boolean, and null to null (or unknown, depending on strictness settings). The interesting decisions happen at the structural level. An object becomes an interface whose properties are the inferred types of its values. An array is trickier — if every element has the same shape, the array is T[]; if elements are objects with different shapes, the converter must decide between a union type, a merged interface, or unknown[].
Mixed-type arrays — say, an array containing both strings and numbers — become (string | number)[]. Arrays of objects with inconsistent shapes are the hardest case: the pragmatic approach is merging every observed key into a single interface, with fields missing from some elements marked optional. This matches how real APIs behave — a summary endpoint returns fewer fields than a detail endpoint, and the merged interface captures the union of both shapes. Without this merging, hand-written types either lie about what fields exist or force you to write defensive checks everywhere.
Interface vs type aliases
TypeScript offers two ways to declare object shapes: interface and type alias (type). Interfaces are open — they can be augmented by declaration merging, which makes them the right choice for types that might be extended later, like API response types that could grow fields. Type aliases are closed but more flexible: they can express unions, intersections, and mapped types directly, which interfaces can't (or can only approximate with workarounds).
For JSON-generated types specifically, interfaces are the conventional default for object shapes — they read naturally, support the `extends` keyword for inheritance, and play nicely with declaration merging if a project needs to add custom fields. Type aliases shine for the structural plumbing around them: union types for mixed arrays, type aliases for computed names, and tuple types for fixed-position arrays. Good generators emit interfaces for objects and reserve type aliases for cases that genuinely need them.
Handling nested objects, arrays and edge cases
Nesting is where generators earn their keep. A deeply nested JSON document — a user with an address object containing a geo object, plus an array of order objects, each with line items — produces a tree of types when converted recursively. Each nested object gets its own named interface, named from the property it was found under (address becomes Address), so the output stays navigable instead of becoming one enormous inline type. The root gets a name you choose, and the nested names derive from the data itself.
Edge cases separate useful generators from toys. Keys that aren't valid identifiers — user-id, 2fa_enabled, or keys with spaces — must be quoted in the output, since TypeScript allows arbitrary string keys in interfaces only with quoted names. Empty objects need a policy (unknown, or an interface with an index signature). Arrays with no elements default to unknown[] or any[]. And null values, which in strict mode are distinct from undefined, need care: a field that's sometimes null and sometimes an object becomes a union (MyType | null) rather than collapsing to one type.
Type safety benefits and workflow
The payoff for generating types is compile-time safety against a moving target. APIs change — fields are added, renamed, or made nullable — and without types, those changes surface as runtime errors in production: undefined is not a function, or a silently missing field rendering as blank. With generated types, breaking changes surface at the moment you compile, with the exact file and line where the old shape is assumed. The types act as a contract, and the compiler enforces it.
The workflow that gets the most value: generate types from a real sample response (not a synthetic one), commit them alongside the code that consumes the API, and regenerate when the API changes — the diff in the generated types is a changelog of what the API actually did. Tools that generate from live endpoints or OpenAPI schemas automate this further, but even paste-based generation catches the majority of issues. The one discipline that matters: generate from a response that represents the full shape, including optional fields, or your types will be narrower than reality and the compiler will enforce a fiction.
Frequently asked questions
How are arrays with mixed item shapes handled?
If every element is an object, their keys are merged into a single interface (fields missing from some elements are still included). If elements are different primitive types, the result is a union type array.
Does it handle deeply nested objects?
Yes — every nested object gets its own named interface, generated recursively from the property name it was found under, so the output stays readable rather than one giant inline type.
What happens with a key that isn't a valid identifier?
Keys that aren't valid TypeScript identifiers (like ones with spaces or hyphens) are automatically quoted in the generated interface, e.g. "user-id": string;.