Structs for encoding/json
Decoding JSON in Go means declaring a struct for every object, with an exported field and a json:"…" tag per key. For a large API response that is a lot of typing, and a single wrong tag silently leaves a field empty. Generating the structs from a real payload gets the shape and the tags right first time. Paste a response body, a config file or a webhook sample.
Type mapping
- JSON objects become
structtypes; nested objects are separate named structs (Address,Order), referenced by value. - Field names are exported PascalCase versions of the keys, with the usual Go initialisms (
idbecomesID,created-atbecomesCreatedAt). The tag always carries the original key. - Whole numbers become
int64and anything with a fraction or exponent becomesfloat64. A value written1.0counts as a float, so sample data with.0decimals is typed correctly. - Strings and booleans map to
stringandbool; arrays map to slices such as[]stringor[]Order. - Objects inside an array are merged into one struct. A key missing from some items becomes a pointer with
omitempty(*stringjson:“email,omitempty”``), and a key that is sometimesnullbecomes a pointer, so “absent” and “zero” can be told apart. - A key that is only ever
null, or an empty array’s element, becomesinterface{}. - An array mixing types, such as
[1, "a"], gets a small union struct withUnmarshalJSONandMarshalJSONmethods, plus the helper functions they need.
A top-level array produces type Root []RootElement. The output also includes an UnmarshalRoot function (named after your root type) and a Marshal method for convenience.
Options
Root type name (default Root) is the name of the top-level struct, converted to PascalCase. Package (default main) sets the package clause, so the file can go straight into, say, internal/api with api as the package. An invalid package name falls back to main.
Edge cases handled for you
Some JSON keys cannot be expressed naively in a struct tag. A key that is literally - would make encoding/json skip the field, so it is written as json:"-,". A key containing a backtick is written with an interpreted string literal instead of a raw one. Keys whose name starts with a non-Latin letter (名前) get an X prefix so the field is exported and actually decoded. Empty keys and keys containing a comma cannot be named in a json tag at all; a warning tells you which field will not decode.
Integers beyond 2^53 in the sample are typed as float64 with a warning — switch them to int64, json.Number or string if you need exact values. Dates stay string; change them to time.Time if the format is RFC 3339. The result is gofmt-formatted and is generated entirely in your browser. Pair it with cURL to Go to get the request code too.
Examples
API response with nested data
Generates Root, Address and Order structs in package billing, with id as int64 and score and total as float64.
{
"id": 1042,
"name": "Aisha Tan",
"score": 98.5,
"tags": ["admin", "beta"],
"address": { "city": "Singapore", "postcode": "018956" },
"orders": [
{ "id": "ord_1", "total": 129.9, "paid": true },
{ "id": "ord_2", "total": 79, "paid": false }
]
}// To parse and unparse this JSON data, add this code to your project and do:
//
// root, err := UnmarshalRoot(bytes)
// bytes, err = root.Marshal()
package billing
import "encoding/json"
func UnmarshalRoot(data []byte) (Root, error) {
var r Root
err := json.Unmarshal(data, &r)
return r, err
}
func (r *Root) Marshal() ([]byte, error) {
return json.Marshal(r)
}
type Root struct {
ID int64 `json:"id"`
Name string `json:"name"`
Score float64 `json:"score"`
Tags []string `json:"tags"`
Address Address `json:"address"`
Orders []Order `json:"orders"`
}
type Address struct {
City string `json:"city"`
Postcode string `json:"postcode"`
}
type Order struct {
ID string `json:"id"`
Total float64 `json:"total"`
Paid bool `json:"paid"`
}
Webhook events with optional fields
Fields missing from one event become pointers, reason is an interface{} because it is only ever null, and the root is a slice type.
[
{ "type": "invoice.paid", "created": 1789475200, "amount": 12990, "customer": "cus_42" },
{ "type": "invoice.voided", "created": 1789478800, "reason": null }
]// To parse and unparse this JSON data, add this code to your project and do:
//
// events, err := UnmarshalEvents(bytes)
// bytes, err = events.Marshal()
package main
import "encoding/json"
type Events []Event
func UnmarshalEvents(data []byte) (Events, error) {
var r Events
err := json.Unmarshal(data, &r)
return r, err
}
func (r *Events) Marshal() ([]byte, error) {
return json.Marshal(r)
}
type Event struct {
Type string `json:"type"`
Created int64 `json:"created"`
Amount *int64 `json:"amount,omitempty"`
Customer *string `json:"customer,omitempty"`
Reason interface{} `json:"reason"`
}
Common errors and how to fix them
| Error | Cause | Fix |
|---|---|---|
The JSON key "a,b" cannot be named in a Go json tag (empty or containing a comma); that field will not be decoded | A warning: encoding/json uses commas to separate tag options, so the key cannot be matched. | Decode that object into a map[string]any, or implement UnmarshalJSON for the struct. |
Some integers are larger than 2^53; they are typed as floating-point numbers. Use a 64-bit or big-integer type if you need exact values.Explained | A warning: the sample holds integers too large for exact inference. | Change the field to int64 (if it fits), uint64, json.Number or string. |
Trailing comma before '}'Explained | The sample is not valid JSON. | Remove the trailing comma, or repair the sample in the JSON formatter first. |
Frequently asked questions
Why are some fields pointers?
A pointer distinguishes “missing or null” from the zero value. Fields that are absent from some samples or null in some get a pointer type; required fields stay plain values.
Why int64 rather than int?
int64 has the same size on every platform, so the struct decodes identically everywhere. Change it to int if you prefer.
Can I use the structs with other JSON libraries?
Yes. Libraries such as jsoniter, sonic and goccy/go-json read the same json struct tags.
Does the generated code need external packages?
No. It only imports encoding/json, plus bytes and errors when a union type needs helper functions.