Convert JSON to Go structs

Paste JSON and get typed models for Go, Python, C#, Java, Kotlin, Rust or Swift. List items are merged and numbers are typed from how they are written. Nothing is uploaded.

A Go program that reads a JSON API response needs structs whose fields carry json tags, because encoding/json only fills exported fields and matches the key through the tag. Writing them by hand for a nested response is slow and easy to get wrong, so this page does it from a sample. Each nested object becomes a named struct, a key such as user_id becomes UserID with the tag json:"user_id", and the common initialisms ID, URL, API and UUID are written in capitals as Go style asks. A key that is missing from some list items gets omitempty, and a pointer when it is a scalar or a struct, so you can tell a missing value from a zero value; a missing list stays a slice, which is nil when the key is absent, and an ISO timestamp becomes time.Time. A field that holds different types, or only null, becomes any with a comment. The sample opens with a nested object, a list of orders and a timestamp, so you can see the result at once. Always check optional fields against your real data.

How it works

  1. Paste a JSON sample
  2. Pick the language and the root name
  3. Copy or download the code

What the tool generates

To turn JSON into typed code, paste an object or a list of objects, choose Go, Python, C#, Java, Kotlin, Rust or Swift, and copy the generated structs, models or classes. Every nested object becomes its own type, named from its key in PascalCase, so the key orders gives a type Order when it holds a list. The sample opens as Go: id becomes ID int64 with the tag json:"id", user_name becomes UserName, and the ISO timestamp created becomes time.Time. Go gets json tags, Python gets Pydantic v2 BaseModel classes or dataclasses, C# gets records or classes with System.Text.Json attributes, Java gets records or plain classes with optional Jackson @JsonProperty annotations, Kotlin gets data classes with optional kotlinx.serialization annotations, Rust gets serde structs with Serialize and Deserialize derives, and Swift gets Codable structs with CodingKeys where a name differs from the key.

How shapes and numbers are inferred

The tool reads every item of a list and merges their shapes, because one sample item rarely shows the whole truth. A key that is missing from some items is optional: a Go pointer (a slice stays a nil-able slice) with omitempty, a Python Optional with a None default, a C# or Kotlin nullable type, a Rust Option with serde default. A key whose value is null in some items is nullable but still required. If one field holds a number in one item and text in another, no single type fits, so it falls back to the language's any type with a comment saying the types were mixed. An empty list has no items to read, so its element type is also the any type. Numbers are typed from how they are written, not from their value: 7 is an integer, while 1.0 and 1e3 are floats, because a strict integer decoder rejects them. An integer outside the signed 64-bit range, such as 18446744073709551615, gets the language's big type (Go *big.Int, C# and Java BigInteger, Swift Decimal, Rust serde_json::Number) instead of being rounded through a double. A field that mixes such an integer with decimals gets a lossless decimal type (json.Number, BigDecimal, Decimal, JsonElement), never a double, and a Swift integer of more than 38 digits is typed String because Decimal would round it.

Names, keywords and dates

Field names follow each language's style: PascalCase in Go and C#, camelCase in Java, Kotlin and Swift, snake_case in Python and Rust. Go uses its initialisms, so id becomes ID and url becomes URL. A key that is a keyword in the language gets a safe name and the original key is kept: a trailing underscore for Rust (including gen, reserved since the 2024 edition), Python and Java, backticks for Kotlin and Swift. Go struct tags cannot name every legal JSON key: a key of - is written as the tag "-,", while a key with a comma, a quote or nothing at all cannot be a tag name, so that field is marked with a comment. Generated type names avoid the names the output depends on, such as None in Python or List in Java. Two keys that give the same name get a number, and a key that starts with a digit gets a prefix. With Detect timestamps on, only full RFC 3339 timestamps such as 2024-05-01T10:30:00Z (a real calendar day, seconds, and a Z or offset) become time types. Date-only values stay strings. Rust keeps String so you do not need a date crate.

Limits

The sample decides everything: a field that is always present in your sample is required in the output, so check optional fields against your real data or an API specification. The tool does not know enums, string formats other than RFC 3339 timestamps, or value ranges. It accepts up to 500,000 characters of strict JSON and nested data up to 60 levels, and pastes over 20,000 characters are converted in a background worker so the page stays responsive. The output targets Go 1.18 or newer (for any), Pydantic v2, C# 9 records, Java 16 records with Jackson 2.12 or newer, kotlinx.serialization, serde 1 and Swift 5 Codable. The work happens in your browser and the JSON is not uploaded or stored; only your option choices are remembered. For TypeScript use the JSON to TypeScript tool.

Frequently asked questions

Why are some Go fields pointers?

A key that is missing in some items is optional, so it becomes a pointer such as *string with omitempty. A nil pointer means missing, and a pointer to an empty string means the key was there and empty. Lists and any stay as they are: a nil slice means missing or null.

Does it use any or interface{}?

It writes any, which needs Go 1.18 or newer. Replace it with interface{} for older versions. any is used for mixed types, null-only fields and empty lists.

How are time values handled?

Strings like 2024-05-01T10:30:00Z become time.Time and the file gets import "time". time.Time reads RFC 3339, so only full timestamps with seconds and a Z or offset are detected. A date-only string such as 2024-05-01 stays a string field.

What happens to keys like "-" or "a,b"?

encoding/json cannot name them in a struct tag. A key of - becomes the tag "-,". A key with a comma, a quote or no characters at all gets json:"-" and a comment, because Go would otherwise skip or rename the field.

Which Go type do large integers get?

Integers up to 9,223,372,036,854,775,807 are int64. A larger one becomes *big.Int and the file imports math/big. Written as 1.0 or 1e3 it is a float64.

How do I convert JSON to a Go struct?

Paste the JSON, leave the language on Go and copy the result. A key id with a number becomes ID int64 with the tag json:"id", and a key missing from some list items becomes a pointer with omitempty (a list stays a slice, which is nil when the key is absent).

How does it decide which fields are optional?

A key is optional when it is missing from at least one object in a list, for example coupon in 1 of 2 orders. Keys that are present in every object, or in a single object sample, are required, even when the value is null in some.

How are numbers typed: 1 versus 1.0?

From the written form. 7 is an integer (int64, long, i64), while 1.0 and 1e3 are floats, and one float in a list makes the whole field a float. An integer beyond 9,223,372,036,854,775,807 gets a big type such as BigInteger.

Can I get Pydantic and dataclass output?

Yes. Choose Python and then Pydantic v2 or dataclass. Pydantic uses BaseModel and Field(alias=...) for keys like user-name, and a dataclass puts fields with defaults last because Python requires it.

Why is a field typed as any?

A field that is only null in the sample, an empty list, or a field that holds a number in one item and text in another gives no single type, so it becomes the any type (any, Any, object, JsonElement, serde_json::Value) with a comment. Add a sample item with a real value to fix it.

Is my JSON uploaded?

No. The code is generated in your browser, in this tab, and no server receives the JSON. Your option choices are saved in this browser; the JSON is not.