Go JSON — Encoding and Decoding JSON with encoding/json and Struct Tags
In this tutorial, you will learn about Go JSON. We cover key concepts, practical examples, and best practices to help you master this topic.
Go JSON handling uses encoding/json for Marshal/Unmarshal with struct tags for field mapping and custom marshaling for complex data.
What You'll Learn
- Marshaling and unmarshaling
- Struct tags for JSON mapping
- Reading/writing JSON files
- Custom JSON marshaling
Why It Matters
JSON is the standard data format. Docker uses JSON for configs. Kubernetes uses JSON/YAML for API. DodaZIP uses JSON for API responses and configuration.
Real-World Use
REST API responses, configuration files, data export/import, service-to-service communication.
flowchart LR
A["JSON"] --> B["Marshal"]
A --> C["Unmarshal"]
A --> D["Encoder/Decoder"]
A --> E["Custom Marshal"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Basic Marshal/Unmarshal
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}
func main() {
p := Person{Name: "Alice", Age: 30, Email: "alice@example.com"}
jsonData, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(jsonData))
var p2 Person
err = json.Unmarshal(jsonData, &p2)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", p2)
}
Pretty Printing
func main() {
p := Person{Name: "Bob", Age: 25}
jsonData, _ := json.MarshalIndent(p, "", " ")
fmt.Println(string(jsonData))
}
Reading JSON from File
type Config struct {
Port int `json:"port"`
Host string `json:"host"`
DB DBConfig `json:"database"`
}
type DBConfig struct {
URL string `json:"url"`
Pool int `json:"pool"`
}
func main() {
data, err := os.ReadFile("config.json")
if err != nil { log.Fatal(err) }
var config Config
err = json.Unmarshal(data, &config)
if err != nil { log.Fatal(err) }
fmt.Printf("Running on %s:%d\n", config.Host, config.Port)
}
Writing JSON to File
func main() {
config := Config{
Port: 8080,
Host: "localhost",
DB: DBConfig{
URL: "postgres://localhost:5432/app",
Pool: 10,
},
}
file, _ := os.Create("config.json")
defer file.Close()
encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
encoder.Encode(config)
}
Streaming JSON
func main() {
// Decode JSON stream
file, _ := os.Open("large.json")
defer file.Close()
decoder := json.NewDecoder(file)
for {
var item map[string]interface{}
if err := decoder.Decode(&item); err == io.EOF {
break
} else if err != nil {
log.Fatal(err)
}
fmt.Println(item)
}
}
Custom Marshaling
type Color struct {
R, G, B uint8
}
func (c Color) MarshalJSON() ([]byte, error) {
hex := fmt.Sprintf("#%02x%02x%02x", c.R, c.G, c.B)
return json.Marshal(hex)
}
func (c *Color) UnmarshalJSON(data []byte) error {
var hex string
if err := json.Unmarshal(data, &hex); err != nil {
return err
}
fmt.Sscanf(hex, "#%02x%02x%02x", &c.R, &c.G, &c.B)
return nil
}
func main() {
c := Color{R: 255, G: 128, B: 0}
jsonData, _ := json.Marshal(c)
fmt.Println(string(jsonData))
var c2 Color
json.Unmarshal([]byte("\"#00ff00\""), &c2)
fmt.Printf("R:%d G:%d B:%d\n", c2.R, c2.G, c2.B)
}
Common Mistakes
1. Unexported Fields
type Person struct {
Name string // Exported: marshals as "Name"
age int // Unexported: silently skipped!
}
2. Wrong Struct Tags
type Person struct {
Name string `json:name` // Missing quotes! Won't compile
}
3. Pointer vs Value
type Config struct {
Name *string `json:"name,omitempty"`
// Use pointer to distinguish null from empty string
}
4. Not Handling Unknown Fields
decoder := json.NewDecoder(file)
decoder.DisallowUnknownFields() // Strict parsing
5. Forgetting json tags for camelCase
Go expects PascalCase fields. Add json:"field_name" tags for camelCase or snake_case output.
Practice Questions
1. What does omitempty do? Omits the field from JSON if it has zero value. Useful for optional fields.
2. How do you handle nested JSON? Define nested struct types that match the JSON structure. json.Unmarshal handles nesting recursively.
3. What is json.RawMessage? A raw JSON value stored as []byte. Useful for deferred Parsing or heterogeneous data.
4. How do you parse dynamic JSON? Use map[string]interface{} or json.RawMessage. Prefer structs for type safety.
Challenge: Write a function that reads a JSON array of users and returns only active users.
Solution
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Active bool `json:"active"`
}
func filterActive(data []byte) ([]User, error) {
var users []User
if err := json.Unmarshal(data, &users); err != nil {
return nil, err
}
var active []User
for _, u := range users {
if u.Active { active = append(active, u) }
}
return active, nil
}
FAQ
{{< faq question="What is the difference between Marshal and MarshalIndent?" >}} Marshal produces compact JSON. MarshalIndent adds indentation for human readability. {{< /faq >}}
{{< faq question="How do I convert JSON to map?" >}}
Use var data map[string]interface{} with json.Unmarshal. Access values with type assertions.
{{< /faq >}}
{{< faq question="How do I handle time.Time in JSON?" >}}
time.Time marshals to RFC3339 by default. Use json:"timestamp" tag and custom format with MarshalJSON.
{{< /faq >}}
{{< faq question="Can I JSON encode private fields?" >}} No. Only exported fields (PascalCase) are marshaled. Use getter/setter methods if needed. {{< /faq >}}
{{< faq question="What is json.Decoder vs json.Unmarshal?" >}} Decoder streams from io.Reader, useful for large data. Unmarshal works on []byte in memory. Decoder is more memory-efficient. {{< /faq >}}
Try It Yourself
package main
import (
"encoding/json"
"fmt"
)
type Product struct {
Name string `json:"name"`
Price float64 `json:"price"`
}
func main() {
jsonData := `{"name": "Widget", "price": 9.99}`
var p Product
json.Unmarshal([]byte(jsonData), &p)
fmt.Printf("%s: $%.2f\n", p.Name, p.Price)
}
Expected output:
Widget: $9.99
What's Next
Now that you understand JSON, explore building HTTP servers in Go.
| Topic | Description | Link |
|---|---|---|
| Go HTTP Servers | Building web servers | {{< ref "27-http-server" >}} |
| Go File I/O | Reading and writing files | {{< ref "25-file-io" >}} |
| Go Database | SQL database operations | {{< ref "29-database-sql" >}} |