Skip to content
CornerTools

JSON to Code

Generate TypeScript interfaces, Go structs, Python classes and more from sample JSON.

1
1
Characters: 0

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-name or 2fa, 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 line becomes OrderLine. Inner types are named after their fields, arrays in the singular: items holds Item.
  • 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 Date or time.Time. Rust has no such switch: its types keep dates as String.
  • TypeScript — Type aliases writes type rather than interface; Read-only marks every property readonly. 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

Optional means a sample lacks the field; nullable means a sample has it set to 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.
JSON has one number type, and the reading of a sample keeps the value, not its spelling: 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.
Integers that fit 64 bits keep an integer type, such as 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