Tools
JSON to TypeScript
Paste JSON, get TypeScript interfaces that tell the truth —? and | null kept distinct, your real keys preserved, optional runtime validators. Runs entirely in your browser.
Generated code
More on this site: all tools ·JSON formatter ·regex checker ·the arcade
Beta — the generated code is unit- and compile-tested, but inference from one sample can't be perfect; on rare occasions the output may need a tweak. Spot something wrong?Tell me.
What is this tool?
A free JSON-to-TypeScript converter. Paste any JSON document and it generates interfaces (or type aliases) for the whole tree — nested objects become their own named types, arrays become typed lists, and every place your sample was too thin to type is flagged with a warning instead of silently guessed. Optionally it also emits runtime validators: plain functions that check the shape of unknown data and throw with the exact JSON path that failed.
Your JSON never leaves the browser. Inference and generation are pure client-side string work; the page functions offline once loaded.
How to use it
- Paste JSON on the left — the TypeScript regenerates as you type.
- Name the root type; nested types are named from their keys (
users: […]→User). - Choose interfaces or type aliases — same shapes either way.
- Flip on runtime validators if the data crosses a trust boundary (an API you don't own, a file a user uploads).
- Copy or Download the
.tsfile.
Why the field names mirror your JSON exactly
Most converters "helpfully" rename created-at tocreatedAt — and quietly break the contract, becauseJSON.parse returns the original keys. Casting that object to a renamed interface produces types that lie:response.createdAt compiles and isundefined at runtime. This tool keeps your keys verbatim, quoting the ones that need it:
export interface Response {
id: number;
/** ISO 8601 date-time string */
"created-at": string;
}
const r = JSON.parse(raw) as Response; // actually true
r["created-at"]; // stringThat's what makes as Response honest. If you want camelCase domain objects, that's a mapping step — one this page won't pretend doesn't exist.
? and | null are not the same thing
TypeScript can express the difference between a key that may beabsent and a key that is present but null — and JSON APIs routinely use both. This converter keeps them apart, because they behave differently in code:
| Sample evidence | Generated | What it means |
|---|---|---|
| key missing from one object in a list | last_login?: string; | "last_login" in obj may be false; obj.last_login may be undefined |
value explicitly null | note: string | null; | the key is always there; its value may be null |
| both observed | x?: string | null; | brace for either |
Flattening these into one (as most converters do) costs you real checks: obj.x === null and obj.x === undefined are different bugs.
Why dates stay string
An ISO timestamp in JSON is still a string afterJSON.parse — nothing revives it into aDate. Typing it Date would be another lie. Detected date fields are typed string and marked with a /** ISO 8601 date-time string */ doc comment, so you know to new Date(x) at the use site (or in your mapping layer).
The runtime validators — a poor man's zod
Types vanish at runtime; data from outside doesn't care about your interfaces. With Runtime validators on, each type gets a checker:
const user = toUser(await res.json());
// TypeError: expected an integer at $.users[3].id, got a stringThe checks match the inference exactly — integers are verified with Number.isInteger, optional keys may be absent, nullable ones may be null — and the thrown message carries the JSON path, which is most of what you reach for zod for. When you outgrow them (transformations, unions, custom refinements), graduate to a real schema library; until then, zero dependencies.
Interface or type alias?
For object shapes they're interchangeable; pick your codebase's house style. Interfaces give marginally better error messages and can be augmented; type aliases compose with unions and mapped types. The tool emits either — the shapes are identical.
FAQ
Is it free? Yes — free, no sign-up, no limits.
Is my JSON uploaded anywhere? No — fully client-side, works offline after load, and only your last input is kept (in your own browser's local storage).
Why did a field come out unknown?The sample never showed its type: always null, an empty array, or two samples with irreconcilable types. The warning names the path.unknown (not any) is deliberate — it forces a check before use instead of disabling the type system.
Why number for both 1 and1.5? TypeScript has no integer type. The distinction isn't wasted though — the runtime validators check integer fields with Number.isInteger, and the siblingDart,Python,Go,Kotlin andSwift outputs type them properly.
Can it read a JSON Schema? Not yet — v1 infers from samples; schema input and zod output are planned.
Built from scratch for this site in vanilla TypeScript — a pure, unit-tested inference engine with golden-tested emitters and zero runtime dependencies.