Go JSON Marshalling and Unmarshalling: Complete Guide

Go JSON Marshalling and Unmarshalling: Complete Guide
How Does Go Handle JSON?
Go's encoding/json package converts between Go structs and JSON using reflection and struct tags. Marshalling (json.Marshal or json.NewEncoder) converts a Go struct into a JSON byte slice or stream. Unmarshalling (json.Unmarshal or json.NewDecoder) parses JSON into a Go struct. Struct tags like `json:"field_name,omitempty"` control the mapping between Go field names and JSON keys.
Working with JSON in Go
If you are building a web application or a microservice, you are almost certainly communicating via JSON. Go's standard library provides the encoding/json package, which uses reflection to automatically map between JSON text and Go data structures.
In this module, we will explore how to send and receive JSON data like a professional Go developer.
The Marshalling Vernacular
Struct Tags: The Mapping Secret
Go uses "Struct Tags" to define exactly how a field should be represented in JSON. This allows your Go code to use PascalCase while your JSON uses snake_case.
Marshalling Go to JSON
To send JSON in an HTTP response, you transform your data structure using json.Marshal.
Unmarshalling JSON to Go
When receiving data from an API request, you need to "decode" the incoming JSON body into a Go struct.
JSON Power Tools
`json:"bio,omitempty"`Tells Go to skip this field in the JSON output if it has its 'Zero Value' (like an empty string or 0).
json.RawMessageUse this to delay decoding a specific part of a JSON object until later. Great for highly dynamic APIs.
map[string]interface{}The fallback for when you don't know the JSON schema in advance. Allows you to parse arbitrary JSON into a map.
Performance: Encoders vs. Marshals
Wait! There are two ways to handle JSON in Go. Which one should you use?
| Task / Feature | json.Marshal | json.NewEncoder |
|---|---|---|
| Primary Use | When you need the JSON as a variable (string or []byte) | When you want to write JSON directly to a stream (HTTP or File) |
| Memory | Higher (Allocates a new buffer for the string) | Lower (Writes directly to the destination) |
| Simplicity | One-step conversion | More efficient for large data sets |
Validating Incoming JSON
The encoding/json package does basic type validation during decoding, but it won't enforce business rules (like a required field being non-empty). For production APIs, validate the struct after decoding:
For larger applications, the go-playground/validator package provides struct tag-based validation (like `validate:"required,email"`) that eliminates the boilerplate.
Custom Marshalling with MarshalJSON
Sometimes you need to control exactly how a struct is serialised to JSON. Implement the json.Marshaler interface by defining a MarshalJSON() ([]byte, error) method:
Similarly, implement UnmarshalJSON([]byte) error for custom parsing logic.
Handling Nullable Fields
Go's zero values (0, "", false) are not the same as JSON null. If you need to distinguish between a field being absent versus present-but-zero, use pointer types:
When Bio is nil, it marshals to null in JSON. When it's &"", it marshals to "". This pattern is essential for PATCH endpoints where you need to know which fields the client actually sent.
JSON and the Go Standard Library: A Format Overview
Understanding JSON in the context of other formats Go supports:
| Format | Package | Use Case |
|---|---|---|
| JSON | encoding/json | REST APIs, config files |
| XML | encoding/xml | Legacy SOAP services |
| CSV | encoding/csv | Tabular data export |
| Gob | encoding/gob | Go-to-Go binary communication |
| Protocol Buffers | google.golang.org/protobuf | High-performance microservices |
For REST APIs serving web and mobile clients, JSON is the clear choice. Protocol Buffers are used in performance-critical microservice-to-microservice communication.
Further Reading
For a broader understanding of REST API design with Go, see our REST API tutorial and Go REST API project guide. For storing the data you receive as JSON, the next step is Go database connectivity with SQL and GORM. For understanding JSON as a format beyond Go, see our JSON format tutorial.
Next Steps
JSON is how we talk to clients, but how do we store that data permanently? In our next tutorial, we will explore Database Connectivity, learning how to connect our Go web servers to SQL databases using the standard library and ORMs like GORM.
Common JSON Mistakes in Go
1. Unexported fields are silently ignored
json.Marshal skips fields that start with a lowercase letter. Always export fields you want in the JSON output and use struct tags to control the key name: Name string \json:"name"``.
2. Using interface{} for JSON values you know the shape of
Unmarshalling into map[string]interface{} works but forces type assertions everywhere downstream. Define a concrete struct — the compiler will catch shape mismatches at build time, not runtime.
3. Forgetting omitempty causes zero values to appear
Count int \json:"count"`always serialises, even as"count": 0. Add omitempty (json:"count,omitempty") to omit zero values from the output. Note: omitemptyomits the zero value of the type —false, 0, "", nil`.
4. Using - to skip a field vs omitempty
json:"-" always excludes the field. omitempty excludes it only when empty. Use - for fields that should never appear in JSON output (internal state, sensitive data).
5. Time zone information lost in time.Time marshalling
time.Time marshals to RFC3339 format, which includes timezone. But if you parse a time string without a timezone offset, it defaults to UTC. Always be explicit about timezones when serialising time values. See the encoding/json package documentation.
Frequently Asked Questions
How do I marshal a Go struct to a JSON string?
Use json.Marshal(v) which returns []byte and an error, or json.MarshalIndent(v, "", " ") for pretty-printed output. Convert to string with string(b). Always check the returned error.
How do I handle JSON fields that may or may not be present?
Use a pointer type for the field: Name *string \json:"name"`. If the field is absent from the JSON, the pointer will be nil`. If present, it will point to the parsed value.
Can I customise JSON serialisation for a type?
Yes — implement the json.Marshaler interface by adding a MarshalJSON() ([]byte, error) method to your type, and json.Unmarshaler by adding UnmarshalJSON([]byte) error. This gives you full control over the JSON representation.
