What this tool does
The converter reads your JSON once and builds one type for every position in it. All the objects in an array are merged into a single struct, so a key that only some items have becomes an optional field, and a key that is sometimes null becomes nullable. With Optional fields set to Pointers (the default), those fields are pointers such as *string or *int64, which lets you tell a missing or null value from an empty string or zero; with Values they stay plain types and the zero value stands in for missing. Only fields that can be missing get ,omitempty, so a struct you marshal again does not drop real zeros. Whole numbers become int64, numbers with a decimal point or exponent become float64, and a field that mixes the two is float64. Integers above int64 become uint64, and anything beyond 64 bits becomes json.Number, which keeps the digits. A field whose values have different kinds (a string in one item and a number in another) becomes any, and so does a field that was only ever null or an empty array, with a comment saying why. Nested objects with the same keys and types share one struct, named after the key they were found under (two address objects become one Address). Keys become exported Go names with the common initialisms upper case, and a key that encoding/json cannot match from a struct tag, such as one containing a comma or a quote, is listed in a comment instead of being silently dropped. The output is formatted the way gofmt formats it.
Example input and output
{"id": 42, "login": "ada", "avatar_url": "https://example.com/a.png", "profile": {"bio": "Engineer", "site_url": "https://ada.dev"}, "repos": [{"name": "engine", "stars": 12}]}package main
type Root struct {
ID int64 `json:"id"`
Login string `json:"login"`
AvatarURL string `json:"avatar_url"`
Profile Profile `json:"profile"`
Repos []Repo `json:"repos"`
}
type Profile struct {
Bio string `json:"bio"`
SiteURL string `json:"site_url"`
}
type Repo struct {
Name string `json:"name"`
Stars int64 `json:"stars"`
}id, avatar_url and site_url become ID, AvatarURL and SiteURL, the spelling golint and staticcheck expect. The array item type is named from the key in singular form: repos gives Repo.
[
{"sku": "TSH-S", "qty": 1, "price": 19.99, "note": "gift"},
{"sku": "MUG-1", "qty": 2, "price": 8, "discount": null}
]package main
type Items []Item
type Item struct {
Sku string `json:"sku"`
Qty int64 `json:"qty"`
Price float64 `json:"price"`
Note *string `json:"note,omitempty"`
Discount any `json:"discount,omitempty"` // only null in the sample
}price is 19.99 in one item and 8 in the other, so it is float64. note and discount are missing from one item each, so they get ,omitempty. discount was only ever null, so its type is unknown: replace any with the real type once you know it.
{"event": "deploy", "meta": {"region": "eu-west-1", "attempt": 2}, "steps": [{"name": "build", "ok": true}]}package main
type Event struct {
Event string `json:"event"`
Meta struct {
Region string `json:"region"`
Attempt int64 `json:"attempt"`
} `json:"meta"`
Steps []struct {
Name string `json:"name"`
Ok bool `json:"ok"`
} `json:"steps"`
}Inline structs keep a one-off payload in a single type. Choose named structs when you need to build the nested values yourself, for example in tests.
Common errors and fixes
- json: cannot unmarshal number 1.5 into Go struct field Item.qty of type int64
- Every qty in your sample was a whole number, so the field became int64, but the real data has a fraction. Change the field to float64, or add an item with a decimal value to the sample and convert again: a field that mixes whole and decimal numbers is generated as float64.
- json: cannot unmarshal string into Go struct field Item.qty of type int64
- The API sends this number as a string ("3"). If it always does, add the string option to the tag, json:"qty,string", which reads and writes a quoted number. If it sends both forms, use json.Number or any and convert it yourself.
- Fields are empty or zero after json.Unmarshal, with no error
- encoding/json skips keys it cannot match and leaves the field at its zero value. Check that the tag spells the key exactly and that the field name starts with an upper-case letter: unexported fields are ignored. The generated names are always exported, and names that start with a letter that has no upper case (such as 名前) get an X prefix for that reason.
- json: unknown field "x"
- You decode with Decoder.DisallowUnknownFields and the data has a key that was not in your sample. Add the field to the struct, convert again with a fuller sample, or drop DisallowUnknownFields if extra keys are expected.
- Large IDs come back wrong, such as 9007199254740993 read as 9007199254740992
- Decoding into any or map[string]any stores every number as float64, which only holds integers exactly up to 2^53. Use the typed struct (int64 keeps all the digits), or call Decoder.UseNumber so numbers arrive as json.Number.
- parsing time "2024-01-15" as "2006-01-02T15:04:05Z07:00": cannot parse "" as "T"
- You changed a date field from string to time.Time, but time.Time only reads RFC 3339 timestamps such as 2024-01-15T08:30:00Z. Keep date-only values as string and parse them with time.Parse("2006-01-02", s), or write a type with its own UnmarshalJSON.
Common use cases
- Typing a REST or GraphQL response before calling json.Unmarshal
- Writing structs for webhook payloads from GitHub, Stripe or Slack
- Modelling a JSON config file for a CLI or service
- Creating request and response types for an HTTP handler
- Building fixtures for table-driven tests from real responses
Privacy and security notes
- The JSON is parsed and turned into Go by JavaScript in this browser tab. It is not sent to DevPrix or stored on a server, so it is safe to paste a production response.
- Values are read only to work out types; the generated structs contain your keys but none of your values.
- Share copies a link that carries your input after the # in the URL. Browsers do not send that part to servers, but anyone you give the link to can read the JSON.
Frequently asked questions
Pointers or values for optional fields?
Pointers (*string, *int64) let you tell a missing or null value from an empty string, zero or false, at the cost of nil checks. Values are simpler to use, and the zero value stands in for missing. Slices, maps and any are never made pointers because nil already means missing. Either way, only fields that were missing from some objects get ,omitempty.
Why int64 and float64 rather than int and float32?
int64 holds every integer JSON APIs commonly send, including 64-bit IDs, on every platform; int is 32 bits on some. float64 is what encoding/json uses for numbers anyway, and float32 would lose precision. Change a field to int if you prefer: decoding fails with an error rather than silently truncating if a value does not fit.
Does omitempty leave out empty nested structs?
No. encoding/json never treats a struct value as empty, so omitempty has no effect on it. Since Go 1.24 you can write omitzero instead, which leaves out a struct whose fields are all zero. A pointer to a struct with omitempty is left out when it is nil.
What Go version does the output need?
Go 1.18 or later, because fields of unknown or mixed type use any. On older versions replace any with interface{}; nothing else changes.
How are dates and timestamps typed?
They stay string, because JSON has no date type and APIs format dates in many ways. If a field always holds an RFC 3339 timestamp such as 2024-01-15T08:30:00Z, you can change it to time.Time and encoding/json will parse it. Dates without a time, or other formats, need a custom type.