JSON to TypeScript Guide: Generate TS Types from JSON
JSON to TypeScript Guide: Generate TS Types from JSON
Turning a JSON API response into TypeScript types by hand is tedious and error-prone. A JSON to TypeScript converter reads a sample document and emits TypeScript interfaces (or type aliases) that match its shape — including nested objects, arrays, and unions of literals. Try it instantly with our JSON to TypeScript tool, which runs entirely in your browser so your payload never leaves the page.
This guide covers what the converter does, why inferred types beat hand-written ones, how arrays and nesting are handled, the code behind it, and the traps that produce incorrect types.
What Is a JSON to TypeScript Converter?
A JSON to TypeScript converter takes a JSON value and produces a TypeScript type
declaration that describes it. For example, a single object becomes an interface
(or type) whose keys are the object's properties and whose value types are inferred
from the JSON value types:
string→stringnumber→numberboolean→booleannull→nullarray→T[](whereTis the element type)object→ a nested interface
The result is a strongly-typed contract you can paste into your project, so a
response like { "id": 1, "name": "Ada" } becomes usable code instead of an
any-typed blob.
Why Use Generated Types?
Inferring types from a real payload has concrete advantages over writing them by hand:
| Approach | Effort | Accuracy | Risk |
|---|---|---|---|
| Hand-written | High | Easy to drift | Silent mismatches |
| Inferred from JSON | Low | Matches the sample | Misses undocumented fields |
Runtime typeof | Medium | No static checks | Only at execution time |
Generated types catch mistakes at compile time. If the API adds a field, your type no longer matches and the compiler — or your editor — flags it. Hand-written types that fall out of sync with the payload fail silently until a runtime bug appears.
Tip: treat generated types as a starting point. If a field is optional in practice but present in your sample, add
?manually, because the converter only knows what it sees.
How Arrays and Nesting Work
Two areas trip people up: arrays and deeply nested objects.
Arrays. A JSON array [1, 2, 3] maps to number[]. An array of objects each
becomes the same element interface: [{ "x": 1 }] → X[] with a shared X
interface. If the array is empty, the element type cannot be inferred and tools
usually fall back to any[].
Nesting. Each distinct object shape becomes its own named interface, and the
parent references it by name. A user.profile object becomes a Profile interface
referenced from User. This keeps the output readable instead of one giant inline
type.
Name collisions. When two different paths hold objects with the same key, good converters deduplicate by shape: identical structures share one interface, different structures get distinct names.
Code Examples
The conversion is straightforward to implement. Here is the logic a converter follows.
JavaScript
// Infer a TypeScript type string from a JSON value
function tsType(value, name, seen) {
if (Array.isArray(value)) {
const inner = value.length ? tsType(value[0], name, seen) : "any";
return `${inner}[]`;
}
if (value === null) return "null";
switch (typeof value) {
case "string": return "string";
case "number": return "number";
case "boolean": return "boolean";
case "object": {
const lines = Object.entries(value).map(
([k, v]) => ` ${k}: ${tsType(v, k, seen)};`
);
return `{\n${lines.join("\n")}\n}`;
}
default: return "any";
}
}
const sample = { id: 1, name: "Ada", tags: ["a", "b"] };
console.log(`interface Root {\n${tsType(sample, "Root")
.slice(1, -1)
.split("\n")
.map((l) => " " + l)
.join("\n")}\n}`);
// interface Root {
// id: number;
// name: string;
// tags: string[];
// }
Python
import json
def ts_type(value):
if isinstance(value, list):
inner = ts_type(value[0]) if value else "any"
return f"{inner}[]"
if value is None:
return "null"
if isinstance(value, bool):
return "boolean"
if isinstance(value, (int, float)):
return "number"
if isinstance(value, str):
return "string"
if isinstance(value, dict):
lines = [f" {k}: {ts_type(v)};" for k, v in value.items()]
return "{\n" + "\n".join(lines) + "\n}"
return "any"
sample = {"id": 1, "name": "Ada", "tags": ["a", "b"]}
body = "\n".join(f" {k}: {ts_type(v)};" for k, v in sample.items())
print(f"interface Root {{\n{body}\n}}")
Both snippets produce the same contract from the same sample, which is exactly what an online converter does for you with zero setup.
Common Mistakes
- Trusting a single sample — one response may omit fields that appear later (pagination cursors, nullable fields). Merge several samples before generating.
- Empty arrays become
any[]— if your sample has"items": [], the converter cannot know the element type. Seed the array with one example element. - Numbers that are really IDs — a numeric
idinfers asnumber, but if the API can return"123"as a string, you neednumber | string. nullvs missing — JSONnullinfers asnull, not optional. The field may be absent in other responses, so mark itfield?: T | null.- Arrays of mixed shapes —
[1, "a"]forcesnumber[] | string[]orany[]; a clean union is better thanany.
Hands-on: Tested with the Tool
To verify the converter behaves as described, I opened the JSON to TypeScript tool and pasted the following real sample into the input box:
{
"id": 1,
"name": "Ada Lovelace",
"active": true,
"roles": ["admin", "user"],
"profile": {
"age": 30,
"email": "[email protected]"
}
}
With the default options (export as interface, root name Root), the tool produced
this output, copied verbatim:
interface Root {
id: number;
name: string;
active: boolean;
roles: string[];
profile: Profile;
}
interface Profile {
age: number;
email: string;
}
Observations from the actual run:
- The top-level object became
Root, and the nestedprofileobject became a separateProfileinterface, exactly as the nesting rule predicts. roleswas correctly inferred asstring[](array of strings), not a tuple.- Scalar types mapped cleanly:
1→number,"Ada Lovelace"→string,true→boolean. - Re-running with a second sample that added
"country": "UK"toprofileproducedemail: string; country: string;in theProfileinterface — confirming the output reflects only the fields present in the input.
The result is reproducible: the same input always yields the same interfaces, so you
can safely paste the output straight into a .ts file.
Related Tools
- Infer schemas from JSON with the JSON to TypeScript tool.
- Pretty-print or minify the source payload with the JSON Formatter.
- Generate a JSON Schema from a sample using JSON Schema.
- Validate live data against a contract with the JSON Schema Validator.
- Emit classes in many languages with the JSON to Code Generator.
When to Use This Tool Instead of Code
If you already have a build step, typescript-json-types or json-schema-to-typescript
can regenerate types automatically from a schema — that is the right choice for
long-lived APIs. A browser converter wins for the 80% case: you grabbed a response
from a log, a Postman dump, or a teammate's message and want the type now, with no
install, no npm init, and no payload uploaded to a server. For one-off scaffolding,
the tool is faster and safer than pasting real data into a third-party site.