Example
{
"order_id": 5012,
"customer": {
"id": "c_88",
"email": "lin@example.com"
},
"total": 59.9,
"paid_at": null,
"lines": [
{
"sku": "TSHIRT-M",
"qty": 2
},
{
"sku": "MUG",
"qty": 1,
"gift_note": "Happy birthday!"
}
]
}
type Order struct {
OrderID int `json:"order_id"`
Customer Customer `json:"customer"`
Total float64 `json:"total"`
PaidAt any `json:"paid_at"`
Lines []Line `json:"lines"`
}
type Customer struct {
ID string `json:"id"`
Email string `json:"email"`
}
type Line struct {
SKU string `json:"sku"`
Qty int `json:"qty"`
GiftNote string `json:"gift_note,omitempty"`
}
A few things to notice:
order_idbecameOrderID, using the standard Go initialism, notOrderId.paid_atwas alwaysnull, so its type isany. Change it to*time.Timeif you know it's a timestamp.gift_noteappears in only one line item, so it getsomitempty.- Identical object shapes share one struct instead of being duplicated.
Decoding JSON with the generated structs
var order Order
if err := json.Unmarshal(body, &order); err != nil {
return fmt.Errorf("decode order: %w", err)
}
fmt.Println(order.Customer.Email, len(order.Lines))
// Encoding back: fields tagged omitempty are skipped when empty
out, _ := json.MarshalIndent(order, "", " ")
Type mapping
| JSON | Go |
|---|---|
| string | string (or time.Time for RFC 3339 timestamps) |
| integer | int |
| decimal number | float64 |
| true / false | bool |
| object | named struct, or an anonymous struct when inlined |
| array | []T ([]any when empty or mixed) |
| sometimes null | *T |
| always null / mixed types | any |
Named vs. inline structs
By default every nested object gets its own named type, which you can reuse and test separately. Enable Inline nested structs to produce a single type with anonymous nested structs instead. That's handy for one-off API responses you only decode in one place.
Tips
-
Large integers: IDs above 253 are safe in Go's
int64, but if the same JSON is consumed by JavaScript, have the API send them as strings and usejson:",string". -
Unknown fields:
encoding/jsonignores keys that aren't in your struct. Usedecoder.DisallowUnknownFields()to reject them. - Working in other languages too? Generate TypeScript interfaces or Pydantic models from the same JSON.
Frequently asked questions
How do I convert JSON to a Go struct?
Paste a JSON sample into the tool above. It creates a struct for every object, picks Go types (string, int, float64, bool, slices), converts keys into exported Go field names, and adds json tags so encoding/json maps the original keys. Copy the result into your .go file.
Why are the field names different from my JSON keys?
Go only encodes and decodes exported fields, which must start with an uppercase letter. The generator converts keys like user_id to UserID, following Go naming conventions for initialisms (ID, URL, HTTP, API). The json:"user_id" tag keeps the mapping to the original key.
When should I use pointers for nullable fields?
Use a pointer (*string, *int) when you need to tell "null or missing" apart from the zero value. A plain int cannot distinguish 0 from null. Pointers are enabled by default for values that are null in your sample. Slices are left as-is because a nil slice already represents null.
What does omitempty do?
With json:"name,omitempty", json.Marshal skips the field when it has its zero value: "", 0, false, nil or an empty slice or map. The generator adds it to keys that are missing from some of the sample objects, because they are optional in the data.
Why is a field typed as any?
The value was always null, or it had different types in different samples (for example a number in one object and a string in another), so no single Go type fits. Replace any with the right type, or with json.RawMessage to decode it later.