Serialization Best Practices
Team conventions for JSON tags, decode limits, validation order, and evolution tests.
Search across all documentation pages
Team conventions for JSON tags, decode limits, validation order, and evolution tests.
Serialization bugs show up as silent data loss, not compile errors.
These rules keep wire formats stable across deploys and client versions.
userId and user_id keys.json, db, and validate tags aligned on shared structs unless legacy schema forces a documented exception. Grep catches drift in review.json:"-" for secrets and internal-only fields. Never log the full struct if sensitive fields exist without redaction.map[string]any for stable public APIs. Maps hide schema from compilers and OpenAPI.r.Body with http.MaxBytesReader before any decode. Reject oversize payloads before allocation attacks succeed.json.NewDecoder on request paths; set DisallowUnknownFields for strict internal/admin APIs only. Public APIs stay permissive for forward compatibility.go-playground/validator (or equivalent) immediately after decode. Serialization is not validation.validator.ValidationErrors to documented problem JSON.Content-Type: application/json; charset=utf-8 on JSON responses. Clients and caches rely on explicit headers.MarshalJSON and UnmarshalJSON when customizing types. Round-trip tests are mandatory.type alias T inside methods.testdata/. CI unmarshals old payloads into new structs.Unmarshal on security-sensitive DTOs. Catch panics and unexpected type combinations early.json.RawMessage envelopes. Forward unknown event types without crashing workers.Tier A: body size limits on handlers, golden JSON tests for public DTOs, and validator on create/update endpoints.
Tag naming is enforced in review unless a linter is configured.
Only structs that cross HTTP and SQL boundaries need json, db, and validate.
Split DTOs when shapes diverge materially.
Stay permissive by default.
Use metrics and OpenAPI diff in CI to catch client typos without breaking old apps.
When responses must hide write-only fields, join columns, or versioned shapes.
Do not reuse database models for public JSON blindly.
gin, echo, and chi still need explicit body limits and validation wiring.
Framework binders do not replace defense in depth.
Protobuf owns the contract on the wire.
Apply the same evolution discipline to transcoded JSON field names documented for browsers.
Yes - package-level validator.New() with custom registrations in init.
Avoid per-request engine creation.
Queue consumers decode JSON the same way as HTTP.
Apply size limits on message bytes from the broker client.
JSON naming, strict vs permissive decode policy, format per boundary, and deprecation timelines.
Link to testdata fixtures.
Copy the team handler template: MaxBytesReader, decode, validate, map errors.
Point engineers to Serialization Basics first.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026