JSON to Code
Generate TypeScript interfaces, Go structs, Python classes and more from sample JSON.
Turns a JSON sample — an API response, a config file, a webhook payload — into types for TypeScript, Zod, Go, Python, C#, Java, Kotlin, Rust, Swift, Dart or JSON Schema. Several samples in an array are merged into one type, so a field one of them lacks becomes optional.
The types come from quicktype, the open-source generator, with settings chosen for code you can paste as it is. Notes under the sample say what it holds that a type cannot show: fields missing from some objects, empty arrays, numbers that lose their spelling. It runs in your browser: nothing you paste or open is sent anywhere.
How to use it
- Language — remembered for your next visit. Keys that are not valid names in the language, such as
user-nameor2fa, are renamed, with the language's mapping (a Go struct tag,@JsonProperty,#[serde(rename)]) so the JSON still reads into the type. - Root type name — the name of the outer type, Root by default, written in the language's case:
order linebecomesOrderLine. Inner types are named after their fields, arrays in the singular:itemsholdsItem. - Detect dates — off by default. On, a field whose strings are dates or times in every sample takes the language's date type, such as
Dateortime.Time. Rust has no such switch: its types keep dates asString. - TypeScript — Type aliases writes
typerather thaninterface; Read-only marks every propertyreadonly. Rust — which traits to derive besides Serde's.
TypeScript, Go and C# get the types alone. Java (Jackson), Kotlin (kotlinx.serialization), Swift (Codable), Dart and Python also get the code that reads and writes the JSON, since without it a renamed key would no longer reach its field.
How the types are inferred
Every value in the samples is read for its kind, and values at the same place are merged: a field present in some samples only is optional, one that is null in some is nullable, and one that is a number in one sample and a string in another becomes a union where the language has one. Integers stay integers unless some sample has a fraction.
A few readings are fixed, because one sample cannot settle them. Objects whose keys look like data — ids, dates, codes — become maps (Record<string, …>, map[string]…) rather than a type with a field per key. Strings stay strings: a field holding "red" and "blue" is not taken for an enum, nor"42" for a number, nor a UUID for its own type, as the next response may hold any other text there.
What one sample cannot tell is whether a field is ever missing, or what an empty array or a null holds when it does have a value. Pasting several responses as one array gives the merge more to go on.
Examples
An orders API response to TypeScript
- Sample JSON
[ { "order_id": 1042, "placed_at": "2026-03-14T09:30:00Z", "customer": { "name": "Asha Rao", "email": "asha@example.com" }, "items": [ { "sku": "MUG-01", "qty": 2, "unit_price": 7.5 } ], "tags": [] }, { "order_id": 1043, "placed_at": "2026-03-14T10:05:00Z", "customer": { "name": "Ben Ortiz", "email": "ben@example.com" }, "items": [ { "sku": "TEE-M", "qty": 1, "unit_price": 18 }, { "sku": "MUG-01", "qty": 1, "unit_price": 7.5 } ], "coupon": "SPRING10", "tags": [] } ]- Root type name
- Order
- TypeScript, default options
export interface Order { order_id: number; placed_at: string; customer: Customer; items: Item[]; tags: unknown[]; coupon?: string; } export interface Customer { name: string; email: string; } export interface Item { sku: string; qty: number; unit_price: number; }
The notes under the sample read: "tags" is an empty array in every sample; "coupon" is missing in 1 of 2 samples.
The same response to Go
- Go, root type name Order
type Order struct { OrderID int64 `json:"order_id"` PlacedAt string `json:"placed_at"` Customer Customer `json:"customer"` Items []Item `json:"items"` Tags []interface{} `json:"tags"` Coupon *string `json:"coupon,omitempty"` } type Customer struct { Name string `json:"name"` Email string `json:"email"` } type Item struct { Sku string `json:"sku"` Qty int64 `json:"qty"` UnitPrice float64 `json:"unit_price"` }
The struct tags keep the JSON names, and the optional coupon is a pointer, so a missing one reads as nil.
Numbers whose spelling JSON.parse loses
- Sample JSON
{ "price": 1.0, "views": 12345678901234567890 }- Go
type Root struct { Price int64 `json:"price"` Views float64 `json:"views"` }
1.0 reads as the whole number 1, so price is an integer; views is past the 64-bit range, so it is a float. The notes read: "price" is written 1.0, a whole number; "views" holds 12345678901234567890, beyond 2^53.
Frequently asked questions
null. TypeScript writes them coupon?: string and coupon: string | null. An API that sends null one day may omit the field the next, and the types follow only what the samples show.1.0 and 1 are the same number. If a field can hold fractions, a sample with one, such as 1.5, makes it a floating-point type. The notes point out such numbers.int64 in Go; larger ones are typed as floating point. JavaScript and TypeScript hold every number as a double, exact only up to 253, so ids past that are often sent as strings, and the notes point out integers past it.See also
- JSON Formatter & Minifier — to check and format the sample first.
- JSON Schema Validator — to check responses against the JSON Schema this tool writes.