JSON to TypeScript Converter
Paste any JSON sample — get clean TypeScript interfaces with nested types and optional fields instantly.
JSON to TypeScript: Why Doing It Right Actually Matters
There's a certain kind of developer pain that comes from staring at a deeply nested JSON payload from some third-party API and thinking, "I'll just use any for now." And honestly, we've all been there. But "for now" has a way of becoming forever, and the moment you've got any scattered across your codebase, you've given up the one thing TypeScript is actually good at: catching mistakes before they bite you in production.
The gap between raw JSON and typed TypeScript interfaces isn't conceptually huge — but when you're dealing with a response that has fifteen nested objects and thirty fields, hand-writing it is tedious enough that most people skip it. That's the exact problem a JSON to TypeScript converter solves, and it's worth understanding what a good one actually does versus what a lazy one does.
What Gets Lost When You Type JSON by Hand
When you manually transcribe a JSON sample into TypeScript interfaces, a few things tend to go wrong. First, you miss fields. You're looking at one example response, you don't notice that metadata appears on some records but not others, and suddenly your interface says metadata: object when it should be metadata?: SomeShape. Second, you mistype. A field that comes back as a number gets typed as a string because you were skimming. Third — and this is the subtle one — you lose the nested structure. Instead of properly creating interface Order with its own interface LineItem, you collapse everything into a flat Record<string, any> and move on.
A proper converter handles all of this automatically. It walks every key recursively, infers primitive types from actual values, promotes array elements into their own named interfaces, and flags null values as optional — because in real APIs, null almost always means "sometimes this field isn't there."
The Optional Field Question: null vs. undefined vs. Missing
This is where TypeScript newcomers often get confused, and where converters diverge in quality. In a JSON payload, a field can be absent entirely, explicitly null, or present with a real value. TypeScript distinguishes between field?: string (may be absent or undefined), field: string | null (always present but possibly null), and field: string (always there, never null).
Most JSON converters take the lazy path: they see null in the sample and emit field: null, which is technically correct but practically useless. A smarter approach marks those as field?: null or field: string | null depending on what the field semantics suggest. Even better, when you provide multiple array elements with slightly different shapes, a good converter merges them — so if one order has a discount field and another doesn't, the resulting interface marks discount as optional rather than pretending it's always there.
Array Types: Simple Cases and the Messy Ones
Most JSON has arrays of homogeneous objects — a list of users, a list of products. That's easy: inspect the element type, create an interface, emit User[]. But real-world APIs send heterogeneous arrays. An "events" array might contain objects shaped like { type: "click", x: number, y: number } and { type: "keypress", key: string }. A naive converter generates a single merged interface with all fields optional. A thoughtful one might emit a union type: (ClickEvent | KeypressEvent)[].
For a single-sample converter (which is what most tools are), you're working with what you've got. The right behavior is to merge all array element shapes, mark any field that doesn't appear in every element as optional, and name the resulting interface something logical based on the parent key — UserOrders, not just Orders or, worse, Interface1.
Naming: The Underrated Part of Code Generation
Generated code is only useful if it's readable. When a converter takes the JSON key user_profile and names the interface user_profile, that's a miss. TypeScript conventions are PascalCase for interfaces, and a converter worth using transforms user_profile to UserProfile, order-line-items to OrderLineItems, and so on. Nested types should carry context from their parent: if the root interface is ApiResponse, a nested object on the data key should be ApiResponseData, not just Data.
This matters more than it sounds. When you've got ten interfaces in a file, generic names like Address and Settings clash. Context-aware naming produces self-documenting code that doesn't require renaming after generation.
The export and readonly Decisions
Two small choices that reveal whether a converter was built by someone who actually uses TypeScript daily. Should interfaces be exported? Almost always yes — you're generating types to share across your codebase, not to keep private. Should fields be readonly? That depends entirely on usage. If you're modeling API response types that get passed around but never mutated, readonly adds a useful constraint. If you're generating types for a form state object, readonly is wrong. Offering both as a toggle is the right call rather than hardcoding either decision.
What a Converter Cannot Do (and What to Do Instead)
A converter works from samples, and samples lie. If your API returns a user object where role is "admin" in your test data but can actually be "viewer" | "editor" | "admin" in production, the converter will generate role: string. That's correct but not as tight as role: "admin" | "viewer" | "editor". String literal union types require domain knowledge the converter doesn't have.
The practical workflow: use the converter to get 80% of the way there in seconds, then manually tighten string fields to literal unions where you know the enumeration, add JSDoc comments for non-obvious fields, and potentially split merged interfaces into discriminated union types where applicable. Treat generated code as a starting scaffold, not final output — but "scaffold in seconds" beats "write by hand for twenty minutes" every time.
TypeScript Interfaces vs. Type Aliases for JSON Shapes
One thing worth knowing: for JSON data shapes, interface and type are largely interchangeable. Interfaces can be merged via declaration merging; type aliases cannot. For generated types from an API response, neither matters — you won't be merging declarations. The convention in most TypeScript codebases is to use interface for object shapes (which is what converters emit) and type for unions and intersections. If you see generated output using type User = { ... }, it's not wrong, just slightly less idiomatic for this use case.
Bottom line: a good JSON-to-TypeScript converter is a time machine. You drop in a payload, get back something that would have taken twenty minutes of careful typing, and you spend those twenty minutes on the actual logic instead. The quality of what it generates — correct optional fields, sensible names, clean nesting — is what separates a tool worth bookmarking from one you use once and forget.