JSON to TypeScript

Paste JSON, get TypeScript interfaces. Everything runs in your browser.

JSON Input
Options
TypeScript Output

This converter turns a sample JSON payload into TypeScript declarations you can paste straight into a project. It is the fastest way to get types for an API you don't control: capture one real response, paste it here, and start from a shape that matches the data rather than one you guessed at. Everything happens in your browser — the JSON you paste is never uploaded, which matters when the sample is a real response with real customer data in it.

How the inference works. The generator parses your JSON, walks the result, and records a structural type for every value: strings, numbers and booleans map to string, number and boolean; null is kept as the null type rather than being thrown away; and every object gets hoisted into its own named declaration. Nested objects are named after the key that holds them, so { "profile": { "city": "London" } } produces an interface called Profile. An array key is singularized first, so "posts": [{…}] names its element type Post rather than Posts.

Arrays with more than one shape. This is where most generators either give up and emit any[] or produce a union so wide it is useless. Here, every element of an array is folded into a single type. Two objects merge structurally: keys present on both sides keep their type, and a key that only some elements carry becomes optional. So this input:

{"users": [{"id": 1, "name": "A"}, {"id": 2, "name": "B", "admin": true}]}

produces:

export interface Root {
	users: User[];
}

export interface User {
	id: number;
	name: string;
	admin?: boolean;
}

Note the ? on admin — one of the two records lacked the key, so it is optional rather than required. Mixing genuinely different kinds of value works the same way: [1, "two", true, null] becomes (number | string | boolean | null)[], and an array mixing objects with primitives keeps the merged object as one union member alongside the rest.

Roots that aren't objects. If the top level of your JSON is an array, there is no single object shape to declare, so the generator emits a type alias plus an interface for the element: export type Root = RootItem[]; followed by interface RootItem. A top-level primitive — a bare number, string or null — becomes a one-line alias. That happens even with the declaration mode set to interface, because an interface can only describe an object type; there is nothing else TypeScript would accept.

Keys TypeScript won't accept as-is. JSON keys are arbitrary strings, and plenty of real APIs ship keys that are not valid TypeScript identifiers. Any key that isn't a bare identifier gets quoted and escaped — "first-name", "2fa", the empty string, keys with spaces or emoji. When such a key names a nested object, the type name is sanitized separately: first-name becomes FirstName, a name starting with a digit is prefixed with an underscore, and a key with no usable characters at all falls back to Value. If two different shapes end up wanting the same name you get Item and Item2 rather than a clash, and if two shapes are structurally identical they collapse onto a single declaration instead of producing near-duplicate interfaces.

The options. Root type name renames the top-level declaration (and the download filename). Declaration switches between interface X { … } and type X = { … } — interfaces support declaration merging and often produce better editor errors, while type aliases compose more freely with unions and intersections. Optional properties controls when a ? is emitted: only when some record in an array omitted the key (the default), also when the value was ever null, or on every property — useful when the payload is a partial update. Unrepresentable values chooses what an empty array or an empty object degrades to: unknown forces you to narrow before use, while any silently opts out of checking. The export and readonly switches control the corresponding keywords, and the last switch types ISO-8601 strings as Date.

A caveat on that last one. JSON.parse returns a string for "2024-01-31T12:00:00Z", not a Date. Typing it as Date is a claim about what happens after parsing — it is correct if you run a reviver, an ORM, or a schema library that converts the field, and a lie if you hand the raw parse output straight to the type. That is why it is off by default.

What a generated type does and doesn't tell you. These types describe the sample you pasted, not the API's contract. A field that happened to be null in your one sample will be typed null; a field that is sometimes absent will look required if every record in your sample had it. The more representative your sample — several records, including the awkward ones — the closer the output gets. For a contract you can rely on, generate from the API's own schema (OpenAPI, JSON Schema) or validate at runtime with a library like Zod or Valibot. This tool is for the common case where no schema exists and you have one response to work from.

Related tools: JSON Formatter to pretty-print or validate the payload first, CSV ↔ JSON Converter if your data starts as a spreadsheet, and Case Converter for renaming keys between snake_case and camelCase.

How do I generate TypeScript interfaces from JSON?

Paste your JSON into the input box. The interfaces appear immediately below it — there is no button to press. Copy them with the Copy button or save them as a .ts file with Download. Nothing is uploaded; the conversion runs entirely in your browser.

What happens with an array containing different shapes?

Every element is folded into one type. Objects merge structurally: a key present on all elements stays required, and a key only some elements carry becomes optional with a ?. Non-object values stay as union members, so [{"a":1},{"b":2},"x"] gives (RootItem | string)[] with a merged RootItem.

Should I generate interfaces or type aliases?

Either compiles. Interfaces support declaration merging and often produce clearer editor errors for object shapes; type aliases compose more freely with unions, intersections and mapped types. Pick whichever matches your codebase, and switch with the Declaration option.

Why is a property typed null instead of string?

Because null is all the sample showed. JSON carries no type information for a null value, so the generator records exactly what it saw. Paste a sample where the field has a real value, or paste several records in an array so the field can be merged into string | null.

How are keys like "first-name" or "2fa" handled?

They are quoted in the output — "first-name": string — which is valid TypeScript and preserves the original key exactly. If such a key names a nested object, the generated type name is sanitized separately: first-name becomes FirstName, and a name starting with a digit is prefixed with an underscore.

Can it type ISO date strings as Date?

Yes, with the "Type ISO date strings as Date" switch. It is off by default because JSON.parse returns a string, not a Date — the option is correct only if something in your pipeline (a reviver, an ORM, a schema library) converts the field after parsing.

Is my JSON sent anywhere?

No. The parser and generator are plain JavaScript running in your browser, with no network request at any point. You can load the page, go offline, and it still works.

This site is vibe coded. The tools here were built largely by AI, so treat what they tell you as a starting point rather than an answer — double-check anything that matters before you rely on it.

Crunchify.net — 98 free tools, no ads, no tracking.