JSON to Go Struct
Generate Go structs for encoding/json or encoding/json/v2 from a JSON sample, with each key in a json tag. Every value is typed, and a key that some objects lack gets omitzero.
About JSON to Go
The objects in the JSON become Go structs that encoding/json or encoding/json/v2 reads the JSON into and writes back. Every field has its key in a tag, as in json:"created_at", so both packages read created_at into CreatedAt: encoding/json matches a key to a tag without regard to case, and encoding/json/v2 by case.
Every value at a place counts, not just the first. A number is an int, an int64 once one is beyond 32 bits, and a float64 when one is written with a fraction or an exponent, as 10.0 and 1e3 are, since both packages refuse those for an integer type. An integer beyond an int64 makes a uint64 when every integer there fits one and none has a minus sign, and a *big.Int otherwise, and a number beyond a float64, such as 1e400, makes a json.Number, which keeps it as written.
The objects at one place, such as the items of a list, make one struct, with the keys in the order first seen. A key that is null in some objects is optional: a pointer, as in *string, or a type that is nil for null as it is, such as a slice, a map or any. A key that some objects lack is optional too, and has omitzero in its tag, so both packages leave it out again when they write the JSON.
A key that holds two kinds of value, such as a string and a number, or only null, is any, and objects that are always empty are a map[string]any. any reads every number as a float64, so where such a key has a number beyond its range, it is a json.RawMessage, which keeps the JSON as written. A note says why, as one does for each number a float64 or any changes, and each null that omitzero leaves out.
Fields and structs are PascalCase with Go's initialisms, so user_id, userId and USER_ID all give UserID, and a name already taken gets a number, as in UserID2. The struct of a list's items is singular, as Order is for orders.
A tag cannot name every key: not the empty key, nor one with a comma, a quote, an apostrophe, a backslash or a backtick. Such a key has no field, and a note says so. For encoding/json, so does a key with a character that Go 1.26 does not read in a tag, such as € or an emoji. A key - is tagged -,, or -,case:strict for encoding/json/v2, since - alone skips the field.
encoding/json/v2 refuses a key that appears twice in an object and half of a surrogate pair, and writes a nil slice as [] and a nil map as {}. For it, a note names each such place in the JSON, and the option that changes it: jsontext.AllowDuplicateNames, jsontext.AllowInvalidUTF8, or json.FormatNilSliceAsNull and json.FormatNilMapAsNull.
When the JSON is an array of objects, the root struct describes them, and a note gives the type that reads the whole array. Choose Every field to make every field optional, for an API that can leave any key out. The code is a whole Go file, with its package clause and imports, laid out as gofmt lays it out. Everything runs in your browser.
For C#, see JSON to C# Class. For Kotlin, try JSON to Kotlin Data Class, and for TypeScript, JSON to TypeScript.
Built and maintained by Meet Shah · Last updated
What this tool is used for
- Generating structs for encoding/json or encoding/json/v2 from an API response, for a Go service, a CLI or a test.
- Getting the json tags right for keys in snake_case, or with characters a Go name cannot hold, such as spaces, dots and dashes.
- Starting the types for an endpoint that has no OpenAPI schema, from a response copied out of the browser or a log.
- Checking, from the notes, what encoding/json/v2 would refuse or write back differently in a payload before moving code to it.
- Seeing which keys only some objects of a list carry, as the optional fields with omitzero.
Frequently Asked Questions
- How do JSON types map to Go?
- Strings are
string, booleansbool, objects a struct and arrays a slice. A number is anint, anint64beyond 32 bits, auint64or*big.Intbeyond anint64, and afloat64when a sample has a fraction or an exponent, as 10.0 and 1e3 do. A key null in some samples is optional, as in*int. - Why is an int only for 32 bits?
- Go's
inthas 32 bits on 386 and 32-bit ARM, where both packages refuse an integer beyond 2147483647 for it, so an integer beyond 32 bits is anint64, which has 64 bits on every platform. - Why does every field have a json tag?
- So that both packages read the key the field was made from:
created_atis read intoCreatedAtthroughjson:"created_at". encoding/json also reads a key into a field whose tag differs from it only in case, soNAMEis read into a field taggedname, but encoding/json/v2 reads only the exact key. - Which JSON package should I choose?
- encoding/json, unless your code uses encoding/json/v2. The structs differ only in the tag of a key
-and the keys a tag can name. encoding/json/v2 matches keys by case, refuses a repeated key and half of a surrogate pair, and writes a nil slice as[]and a nil map as{}; the notes say where. - What does omitzero do here?
- A key that some objects lack has
omitzero, which leaves its field out of the JSON written when it holds its type's zero value, nil for a pointer, a slice or a map, so a missing key stays missing, while a 0, "" or [] the JSON has is written back. A null the JSON has for such a field is left out too, which a note says. - Should every field be optional?
- A sample shows only some of the keys an API can send as null or leave out: here a key that is null is optional, and so is one that some objects lack, with
omitzero. The API contract says which others are. Every field makes them all optional, withomitzero, so a response can leave any key out.
Common errors and gotchas
- Generating from a sample where a decimal field happens to hold a whole number, such as a price of 12: the field is an
int, and both packages then refuse 12.5. Use a sample with a fraction, or write 12.0, which gives afloat64. - Decoding into
map[string]anyoranyand reading IDs from it: every number is afloat64, so an ID such as 9007199254740993 is read as 9007199254740992. A typed field, anint64here, keeps it. - Typing a key that some objects lack as a plain
stringorint: the struct cannot tell a missing key from "" or 0, and writes "" or 0 back for it. Here such a key is a pointer withomitzero, which leaves it out. - Expecting encoding/json/v2 to read what encoding/json reads: it refuses a key that appears twice in an object, and half of a surrogate pair. A note names each place, and
jsontext.AllowDuplicateNames(true)orjsontext.AllowInvalidUTF8(true)reads it. - Tagging a key
-asjson:"-": both packages then skip the field. Here it is-,for encoding/json and-,case:strictfor encoding/json/v2, which refuses-,. - Trusting the type a key has in one response: a number in one response can be a string in the next, which both packages refuse for an
int. Paste several responses as an array, and every value counts.