JSON to TypeScript Interface
Paste any JSON and get accurate TypeScript interfaces with proper optional fields and AI-inferred JSDoc comments.
How it works
- 1Paste JSONAny JSON structure.
- 2ConvertTypeScript interface with JSDoc comments.
JSON to TypeScript: what AI-assisted inference gets right that classic generators miss
Why JSON-to-TypeScript is harder than it looks
Classic JSON-to-TypeScript generators (quicktype.io, json-to-ts) work on syntax: they walk the JSON tree and emit interfaces matching the literal shape. That handles trivial cases like {"id": 1, "name": "Ada"} → {id: number; name: string;}. The problem is real-world JSON: enum-like strings ("status": "pending"), discriminated unions, optional fields, ISO dates encoded as strings, nullable values, and nested arrays of varied shapes.
An LLM-based converter understands semantics, not just shape. It can recognize 'status' fields with values like 'pending'|'done'|'failed' as a literal union, ISO-8601 strings as Date candidates, and consistent null patterns as optional fields. The output is more idiomatic TypeScript that you'd actually want to commit to your codebase.
Null vs undefined vs optional
TypeScript has three ways to represent 'value might not be there': T | null, T | undefined, and the optional marker (field?: T). They mean different things at runtime: null is an explicit value, undefined is the absence of a value, optional means the key might not even be present in the object.
Our converter defaults to 'T | null' when a JSON field has null values, which matches the most common API contract: 'the key is always present, sometimes its value is null'. If you have multiple JSON samples and a key appears in some but not others, the converter switches to 'field?: T'. This is the most defensible default — the alternative (treating every nullable field as optional) leads to runtime bugs where TypeScript thinks the field might not exist when it actually does.
Inferring enum-like fields
When a field's value is one of a small set of strings (typically 2–8 distinct values), the AI infers a literal union type. So a JSON like {"role": "admin"} with neighboring samples showing 'role' = 'user' or 'guest' produces 'role: "admin" | "user" | "guest"' instead of just 'role: string'.
The heuristic that works well: if the cardinality of distinct values is low and the values look like identifiers (no spaces, no punctuation, snake_case or camelCase), it's likely an enum. Free-form strings — names, descriptions, URLs — should stay as 'string'. The AI also recognizes patterns like 'http://' prefixed strings as URLs and ISO-8601 timestamps as Date candidates.
Array typing strategies
JSON arrays in TypeScript usually become 'T[]'. The interesting cases: arrays of mixed types (T1 | T2)[], arrays that are actually tuples ([T1, T2, T3]), and arrays of objects with varying optional fields. For mixed-type arrays, the converter emits a union. For tuples (rare but valid JSON), if the array has a fixed length with predictable types, it can emit a tuple type — but the safer default is a union.
For arrays of objects, we generate a single interface that represents the union of all observed keys, marking inconsistent ones as optional. If one element has 'tags: string[]' and another doesn't have 'tags' at all, the result is 'tags?: string[]'.
JSDoc inference: not just types, but documentation
The AI generates short JSDoc comments inferred from field names and values. A field named 'createdAt' with an ISO timestamp value gets '/** Creation timestamp (ISO-8601). */'. A field named 'email' with a string matching the email pattern gets '/** User email address. */'. These aren't deep documentation, but they're meaningful enough that they help developers reading the type without having to look at the source data.
When in doubt, the AI is conservative — it generates a comment only when the field name + value pattern give a high-confidence signal. Generic fields like 'value' or 'data' don't get an auto-comment because anything we could write would be guessing.
Nested types and recursive structures
Nested objects become named interfaces, not anonymous inline types. So {"user": {"id": 1, "name": "Ada"}} produces interface User { id: number; name: string; } and interface Root { user: User; } — two named types instead of one with inline structure. This is more readable, more refactorable, and matches the way most TypeScript projects organize types.
Recursive structures (a tree node with children of the same type) get a self-referential interface. The AI detects recursion when a nested object has the same shape as its parent. This is rare in flat API responses but common for tree-shaped data like file systems or comment threads.
What the tool can't reliably infer from one example
Single JSON sample limitations: optional vs always-present (we default to T | null), enums with rare values not in your sample (we'll miss them), polymorphic types with discriminator fields, numeric ranges (we say 'number' but don't know if 0–100 or unbounded), and string format patterns beyond email/URL/ISO.
If you provide multiple JSON samples or annotate the result with your knowledge, you get cleaner types. The best workflow: paste a single representative sample, get the draft, then refine by hand for cases the AI couldn't infer. Faster than writing types from scratch.
Frequently asked
Are optional fields detected?
From one JSON example, that's tricky. Null values become 'T | null'. For true optionals, provide multiple examples.
Are enums detected?
Yes — fields with clearly few string values become literal union types.
Get new tools first.
One tool per week. No ads. Unsubscribe anytime.