Turn a JSON response into Go structs with the
json tags already written. Field names follow Go's naming conventions, so you get ID and URL
rather than Id and Url.
{
"id": 1,
"name": "Ana",
"orders": [
{ "order_id": 1001, "note": "gift" },
{ "order_id": 1002 }
]
} type User struct {
ID int64 `json:"id"`
Name string `json:"name"`
Orders []Order `json:"orders"`
}
type Order struct {
OrderID int64 `json:"order_id"`
Note *string `json:"note"`
} Go only exports capitalized names. encoding/json matches keys case-insensitively, so name finds Name, but created_at never finds CreatedAt, and without a tag that field silently stays at its zero value. Tags make every mapping explicit.
ID, URL, API, HTTP, UUID. Go's style guide asks for this, and linters such as golint and staticcheck flag Id and Url.
Use pointers when absent and zero mean different things. A *string can be nil, which is the only way to tell a missing field from an empty one. That matters for a PATCH body and not for a read-only response, so it is a toggle.
int is platform-sized. A JSON id that fits on your laptop can overflow on a 32-bit target, so whole numbers come out as int64.
omitempty only affects encoding, and it drops any value that is the zero value for its type. A deliberate false, an intentional 0 and an empty string all vanish from the output along with the fields you meant to omit. If "absent" and "set to zero" mean different things in your API, use a pointer and leave omitempty off.
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type User struct {
ID int64 `json:"id"`
Name string `json:"name"`
Note *string `json:"note"` // nil means the key was absent or null
Orders []Order `json:"orders"`
}
type Order struct {
OrderID int64 `json:"order_id"`
Total float64 `json:"total"`
}
func main() {
res, err := http.Get("https://api.example.com/user/1")
if err != nil {
panic(err)
}
defer res.Body.Close()
var u User
// Decode ignores unknown fields by default. To reject them:
// dec := json.NewDecoder(res.Body); dec.DisallowUnknownFields()
if err := json.NewDecoder(res.Body).Decode(&u); err != nil {
panic(err)
}
fmt.Printf("%+v\n", u)
} Decode silently ignores fields the struct does not mention. That is usually what you want from an API you do not control, and it is also how a typo in a tag can go unnoticed. Call DisallowUnknownFields() on the decoder when you want those reported.
// Some APIs send an id as a number sometimes and a string other times.
type ID struct{ Value string }
func (i *ID) UnmarshalJSON(b []byte) error {
// try string first
var s string
if err := json.Unmarshal(b, &s); err == nil {
i.Value = s
return nil
}
var n json.Number
if err := json.Unmarshal(b, &n); err != nil {
return fmt.Errorf("id: expected string or number, got %s", b)
}
i.Value = n.String()
return nil
}
// json.RawMessage is the other escape hatch: keep the bytes,
// decide how to decode them once you have read a discriminator field. int is platform-sized: 64 bits on most machines today, 32 on some embedded and WebAssembly targets. A JSON id that decodes fine on your laptop can overflow elsewhere, so the generator emits int64 for whole numbers. If you know the range is small, narrowing it by hand is safe and slightly cheaper.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
Yes, on every field. Go only exports capitalized names, and encoding/json matches keys case-insensitively, so name finds Name without help. However, a snake_case key such as created_at never matches CreatedAt without a tag, and that field silently stays at its zero value. The tags make every mapping explicit.
snake_case and kebab-case become PascalCase, and common initialisms are capitalized the way Go's style guide asks: ID, URL, API and HTTP, not Id, Url, Api and Http. Linters such as golint and staticcheck flag the alternatives.
int64 when every sample value is a whole number, float64 when any of them has a decimal point. JSON has one number type, so this is inferred from your sample. Check the field if its range matters.
Tick Pointers for nullable and nullable fields become *string, *int64 and so on. A pointer is the only way to tell absent from zero in Go, which matters for a PATCH endpoint and not for a read-only response. A field that is null in every sample has no type to infer and comes out as interface{}.
The top-level struct is named Root by default here, because JSON has no name for the top-level object. Type a better name in the Root name field. Nested structs are named after the keys they came from.
Only if you are encoding. On the way out, omitempty drops zero values, which is often what you want and occasionally hides a deliberate false or 0. It has no effect when decoding.
The same JSON also becomes a TypeScript interface or a Python dataclass. For a contract you can validate data against, see the JSON Schema generator.