golangtutorialAll tutorials

JSON in Go: Encoding, Decoding, and Validation

Encode and decode JSON safely in Go with struct tags, validation, unknown-field checks, and HTTP examples.

Web Apis · Lesson 1Saved in this browser. No account required.

Go’s encoding/json package maps JSON values to structs, maps, slices, and primitive types. For APIs, structs provide the clearest contract.

Encode a struct

type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email,omitempty"`
}

data, err := json.Marshal(User{ID: 1, Name: "Ada"})

omitempty removes a field when it has its zero value. Avoid using it when clients must distinguish “not supplied” from an explicit zero.

Decode request JSON strictly

func decodeJSON(r *http.Request, dst any) error {
	dec := json.NewDecoder(http.MaxBytesReader(nil, r.Body, 1<<20))
	dec.DisallowUnknownFields()
	if err := dec.Decode(dst); err != nil {
		return err
	}
	if dec.Decode(&struct{}{}) != io.EOF {
		return errors.New("body must contain one JSON value")
	}
	return nil
}

In a real handler, pass the ResponseWriter to MaxBytesReader. Limiting the body prevents an oversized request from consuming unbounded memory. Rejecting unknown fields catches client typos instead of ignoring them.

Validate after decoding

JSON decoding proves that input has the correct representation, not that it is acceptable business data.

type CreateUser struct {
	Name  string `json:"name"`
	Email string `json:"email"`
}

func (in CreateUser) Validate() error {
	if strings.TrimSpace(in.Name) == "" {
		return errors.New("name is required")
	}
	if _, err := mail.ParseAddress(in.Email); err != nil {
		return errors.New("email is invalid")
	}
	return nil
}

Write JSON responses consistently

func writeJSON(w http.ResponseWriter, status int, value any) error {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	return json.NewEncoder(w).Encode(value)
}

Set headers before calling WriteHeader. Do not expose raw internal errors in public JSON responses; map them to stable error codes and log the detailed cause separately.

Numbers and optional fields

Decoding into any converts JSON numbers to float64 by default. Call Decoder.UseNumber when exact numeric representation matters. Use pointer fields or a custom optional type when an update endpoint must distinguish an omitted field from a supplied zero value.