JSON handling
Use the standard library JSON tools carefully, validate boundaries, and avoid loose decoding by default.
Canonical guidance
- default to explicit struct decoding
- validate request boundaries carefully
- decide deliberately whether unknown fields should be rejected
- keep transport-layer JSON handling separate from domain logic
Use when
- HTTP JSON APIs
- config loading
- external payload parsing
Avoid
- decoding arbitrary maps unless flexibility is truly needed
- silently accepting malformed or surprising payloads at API boundaries
- blending decode, validation, and business logic into one large function
Preferred pattern
func decodeCreateUser(r *http.Request) (CreateUserRequest, error) {
defer r.Body.Close()
var req CreateUserRequest
dec := json.NewDecoder(io.LimitReader(r.Body, 1<<20))
dec.DisallowUnknownFields()
if err := dec.Decode(&req); err != nil {
return CreateUserRequest{}, err
}
if err := dec.Decode(&struct{}{}); err != io.EOF {
return CreateUserRequest{}, errors.New("body must contain a single JSON object")
}
return req, nil
}
Anti-pattern
- generic
map[string]anydecoding across normal application paths
Explanation: This anti-pattern is tempting for flexibility, but untyped maps move validation bugs and shape confusion deeper into the application.
Why
- JSON boundaries are where type looseness enters otherwise structured Go code
Related pages
Sources
- encoding/json package - Go Team
- How to parse a JSON request body in Go - Alex Edwards