Quick answer
Go decodes JSON into structs whose exported fields carry json tags naming the original keys. Paste a sample into Playxoft's free JSON to Go converter to generate them: names follow Go conventions, so user_id becomes UserID, whole numbers become int64, RFC 3339 dates become time.Time, and fields that can be null or missing get pointers and omitempty. Then decode with json.Unmarshal into your root struct.
- Go initialisms in names
- json tags on every field
- omitempty and pointers
- int64 or int
- time.Time for dates
- gofmt-aligned output
- Download models.go
How to use the JSON to Go
- 1Paste JSON into JSON sample and set the Root type name.
- 2Set the Package name and choose int64 or int for whole numbers.
- 3Toggle omitempty, Pointers for optional and time.Time to match your codebase.
- 4Copy the structs or download models.go.
- 5Decode with
json.Unmarshal(body, &data)orjson.NewDecoder(r.Body).Decode(&data).
How do Go struct tags map JSON keys?
encoding/json only sees exported fields, the ones whose names start with a capital letter, so a JSON key like user_id needs a Go field with a tag naming the original key:
type User struct {
ID int64 `json:"id"`
Email *string `json:"email,omitempty"`
CreatedAt time.Time `json:"created_at"`
Password string `json:"-"` // never encoded or decoded
Count int64 `json:"count,string"` // a number sent as a JSON string
}Without a tag, Unmarshal still matches keys to field names case-insensitively, so Email would catch email, but user_id would never reach UserID. The generator writes a tag for every field, so the mapping is explicit and survives renames.
How are JSON types mapped to Go?
| JSON in your sample | Go type |
|---|---|
| String | string |
| RFC 3339 date-time | time.Time |
| Whole number | int64 (or int) |
| Whole number beyond int64 | json.Number |
| Decimal number | float64 |
| Boolean | bool |
| Array | []T |
| Object | A named struct |
| Empty or ID-keyed object | map[string]T |
Mixed types, or only null | any |
int64 is the default because IDs and Unix timestamps outgrow 32 bits, and int is only 64 bits wide on 64-bit platforms. Nested objects become separate named structs rather than anonymous inline ones, so you can reuse them, write methods on them and refer to them in tests.
When should I use pointers and omitempty?
Go has no "missing" state: a decoded int64 that wasn't in the JSON is just 0, indistinguishable from a real zero. When the difference matters, use a pointer, where nil means absent or null:
| Field type | Key missing | Value null | Value 0 |
|---|---|---|---|
int64 | 0 | 0 | 0 |
*int64 | nil | nil | Pointer to 0 |
omitempty controls encoding: the field is left out of Marshal output when it holds a zero value, meaning 0, "", false, nil, or an empty slice or map. It never omits a struct value; Go 1.24 added the omitzero option for that. The generator adds pointers and omitempty only to fields that were missing or null in your samples and keeps required fields as plain values.
How do I decode JSON safely in Go?
json.Unmarshal is fine for small payloads. For request bodies, a Decoder gives you more control:
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields() // reject keys the struct doesn't declare
var in CreateUser
if err := dec.Decode(&in); err != nil {
var syntaxErr *json.SyntaxError
if errors.As(err, &syntaxErr) {
log.Printf("bad JSON at byte %d", syntaxErr.Offset)
}
http.Error(w, err.Error(), http.StatusBadRequest)
return
}Decoding into map[string]any turns every number into float64, which loses precision above 2^53. Call dec.UseNumber() to get json.Number values instead, or decode into typed structs like the ones generated here.
Why are field names like UserID and not UserId?
Go style keeps initialisms in a consistent case: ID, URL, HTTP, API, JSON, UUID. That is why user_id becomes UserID and avatarUrl becomes AvatarURL, which is what linters such as staticcheck expect. Keys that start with a digit get a Field prefix (2fa becomes Field2fa), since Go identifiers can't start with one.
The output is aligned the way gofmt aligns it, so it drops into a file without a reformatting diff.
Since Go 1.27, encoding/json runs on the new encoding/json/v2 engine with unchanged v1 behavior (opt out with GOEXPERIMENT=nojsonv2). The v2 API reads the same json tags, but by default it matches names case-sensitively, rejects duplicate keys and invalid UTF-8, and its omitempty omits empty JSON values, so use omitzero for Go zero values.
Frequently asked questions
Last updated by the Playxoft engineering team.