One of the things I love about Go is how practical it is when dealing with JSON.
No complicated setup. No magic. Just clear, predictable code.
The encoding/json package provides two main ways to work with JSON:
- In-memory (Marshal/Unmarshal)
- Stream-based (Encoder/Decoder)
Let’s break it down.
1. Marshal and Unmarshal — for small or simple data
These are the most common functions when your JSON fits comfortably in memory.
▶️ Go → JSON (Marshal)
You convert a Go struct, map, or slice into JSON.
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
user := User{"Hugo", "hugo@example.com"}
data, err := json.Marshal(user)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
Output:
{"name":"Hugo","email":"hugo@example.com"}
Use Marshal when:
- You already have a Go object in memory.
- You want to send or save it as JSON (HTTP response, file, etc.).
- The data is not too large (fits easily in memory).
◀️JSON → Go (Unmarshal)
You convert JSON bytes into a Go struct or map.
jsonData := []byte(`{"name":"Hugo","email":"hugo@example.com"}`)
var user User
err := json.Unmarshal(jsonData, &user)
if err != nil {
log.Fatal(err)
}
fmt.Println(user.Name) // Hugo
Use Unmarshal when:
- You receive JSON as a string or from a file and want to turn it into a Go type.
- The JSON is fully available in memory.
✅ Tip:
Always pass a pointer to the destination (&user), so Go can modify the variable.
2. Encoder and Decoder — for streams or large data
When working with large JSON files or network streams, loading everything into memory isn’t efficient.
That’s where Encoder and Decoder come in — they work with streams (io.Reader and io.Writer).
▶️Go → JSON stream (Encoder)
You write JSON directly to an output, like a file or HTTP response.
user := User{"Hugo", "hugo@example.com"}
file, _ := os.Create("user.json")
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ") // optional, for pretty-print
encoder.Encode(user)
Use Encoder when:
- You want to write JSON directly to a file, socket, or HTTP response.
- You want to avoid keeping the full JSON in memory.
- You’re writing continuous data (e.g., logs, API responses).
◀️ JSON stream → Go (Decoder)
You read JSON progressively from an input, like a file or HTTP request body.
file, _ := os.Open("user.json")
defer file.Close()
var user User
decoder := json.NewDecoder(file)
decoder.Decode(&user)
fmt.Println(user)
Use Decoder when:
- You’re reading large JSON data.
- The data is coming from a network or file stream.
- You don’t want to load everything at once into memory.
Summary
| Direction | Data Type | Function | Best for |
|---|---|---|---|
| JSON → Go | []byte |
Unmarshal |
Small data in memory |
| Go → JSON | []byte |
Marshal |
Small data in memory |
| JSON → Go | io.Reader |
Decoder |
Large files or streams |
| Go → JSON | io.Writer |
Encoder |
Large files or streams |
Go’s encoding/json package is a perfect example of the language’s philosophy:
simple, explicit, and reliable.
When you need something quick and small, use Marshal and Unmarshal.
When you’re dealing with streams, files, or large payloads, use Encoder and Decoder.
You get both simplicity and performance, and you always know exactly what your code is doing.
That’s Go.
Top comments (0)