Go Maps and Structs — map struct Tags and Zero Values Explained
In this tutorial, you will learn about Go Maps and Structs. We cover key concepts, practical examples, and best practices to help you master this topic.
Go maps provide key-value storage with O(1) average lookup, structs group related fields with optional tags for JSON serialization, and both types have zero values for safe initialization.
What You'll Learn
- Creating and using maps
- Struct definition, fields, and tags
- Nested structs and composition
- JSON serialization with struct tags
Why It Matters
Maps and structs are fundamental data structures. Docker uses structs for container configuration and maps for labels. Kubernetes API objects are deeply nested structs. DodaZIP uses structs for archive metadata. Understanding these types is essential for real-world Go.
Real-World Use
A web API handler decodes JSON into structs, stores data in maps for Caching, and returns JSON-encoded struct responses. A CLI tool uses structs for configuration with JSON tags for file serialization.
flowchart LR
A["Maps & Structs"] --> B["Maps"]
B --> C["Structs"]
C --> D["Tags"]
D --> E["JSON"]
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
Maps
Maps are Go's built-in key-value data structure:
// Declare and initialize
var scores map[string]int // nil map
scores = make(map[string]int) // Empty map
// Map literal
scores := map[string]int{
"Alice": 95,
"Bob": 87,
"Charlie": 92,
}
// Access
fmt.Println(scores["Alice"]) // 95
// Add/update
scores["David"] = 88
// Delete
delete(scores, "Bob")
// Check existence
score, exists := scores["Alice"]
if exists {
fmt.Println("Score:", score)
}
Map Operations
ages := map[string]int{
"Alice": 25,
"Bob": 30,
}
// Length
fmt.Println(len(ages))
// Iteration
for name, age := range ages {
fmt.Printf("%s is %d\n", name, age)
}
// Keys only
for name := range ages {
fmt.Println(name)
}
// Values only
for _, age := range ages {
fmt.Println(age)
}
Map with Any Key Type
// Keys can be any comparable type (not slices, maps, or functions)
type Key struct {
ID int
Name string
}
cache := make(map[Key]string)
cache[Key{1, "Alice"}] = "data1"
fmt.Println(cache[Key{1, "Alice"}]) // data1
Structs
Structs group related fields together:
type Person struct {
Name string
Age int
City string
}
func main() {
// Zero value
var p Person
fmt.Println(p) // { 0 }
// Field assignment
p.Name = "Alice"
p.Age = 25
p.City = "New York"
fmt.Println(p) // {Alice 25 New York}
// Struct literal
p2 := Person{
Name: "Bob",
Age: 30,
City: "London",
}
// Positional (not recommended)
p3 := Person{"Charlie", 35, "Tokyo"}
}
Struct Methods
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
func main() {
r := Rectangle{Width: 5, Height: 3}
fmt.Println("Area:", r.Area()) // 15
fmt.Println("Perimeter:", r.Perimeter()) // 16
}
Struct Tags
Tags provide metadata for struct fields, commonly used for serialization:
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
CreatedAt time.Time `json:"created_at"`
Password string `json:"-"`
}
JSON Serialization
package main
import (
"encoding/json"
"fmt"
)
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
func main() {
p := Product{ID: 1, Name: "Widget", Price: 9.99}
// Marshal to JSON
data, _ := json.Marshal(p)
fmt.Println(string(data)) // {"id":1,"name":"Widget","price":9.99}
// Pretty print
data, _ = json.MarshalIndent(p, "", " ")
fmt.Println(string(data))
// Unmarshal from JSON
jsonStr := `{"id":2,"name":"Gadget","price":19.99}`
var p2 Product
json.Unmarshal([]byte(jsonStr), &p2)
fmt.Println(p2) // {2 Gadget 19.99}
}
Nested Structs
type Address struct {
Street string `json:"street"`
City string `json:"city"`
Country string `json:"country"`
}
type Employee struct {
Name string `json:"name"`
Age int `json:"age"`
Address Address `json:"address"`
}
func main() {
emp := Employee{
Name: "Alice",
Age: 25,
Address: Address{
Street: "123 Main St",
City: "New York",
Country: "USA",
},
}
data, _ := json.MarshalIndent(emp, "", " ")
fmt.Println(string(data))
}
Anonymous (Embedded) Fields
type Contact struct {
Email string
Phone string
}
type Person struct {
Name string
Age int
Contact // Embedded struct
}
func main() {
p := Person{
Name: "Alice",
Age: 25,
Contact: Contact{
Email: "alice@example.com",
Phone: "+1-555-0100",
},
}
p.Email = "new@example.com" // Promoted field
fmt.Println(p.Email) // new@example.com
}
Common Mistakes
1. Writing to a Nil Map
var m map[string]int
m["key"] = 42 // Panic! Assignment to nil map
m = make(map[string]int)
m["key"] = 42 // OK
2. Assuming Map Iteration Order
Map iteration order is random in Go. Don't rely on insertion order:
m := map[string]int{"a": 1, "b": 2, "c": 3}
for k, v := range m {
fmt.Println(k, v) // Order varies each run
}
3. Using Struct Without Initialization
type Point struct {
X, Y int
}
var p Point
p.X = 5
p.Y = 10 // OK — zero values are valid
4. Copying a Struct with Locks or Pointers
type SafeCounter struct {
mu sync.Mutex
val int
}
// Copying this struct copies the mutex (don't do it)
// Use pointer receiver instead
5. Forgetting JSON Tags
Without tags, JSON fields match struct field names (capitalized). Always add JSON tags for proper naming.
Practice Questions
1. How do you check if a key exists in a map?
Use the two-value assignment: value, exists := myMap["key"]. If the key doesn't exist, value is the zero value and exists is false.
2. What are struct tags used for?
Struct tags provide metadata for fields, typically for serialization (JSON, XML, YAML), validation, or database column mapping. They're accessed via Reflection.
3. How do you embed a struct in another struct?
Declare it without a field name: type Person struct { Name string; Contact Contact }. The embedded struct's fields are promoted to the outer struct.
4. Can a map have a struct as a key?
Yes, if the struct fields are all comparable (no slices, maps, or functions in the struct).
Challenge: Create a struct for a blog post with JSON tags, nested author struct, and a map of tags. Marshal to JSON and back.
Solution
package main
import (
"encoding/json"
"fmt"
)
type Author struct {
Name string `json:"name"`
Email string `json:"email"`
}
type Post struct {
Title string `json:"title"`
Content string `json:"content"`
Author Author `json:"author"`
Tags map[string]bool `json:"tags"`
Draft bool `json:"draft"`
}
func main() {
post := Post{
Title: "Go Maps & Structs",
Content: "Maps and structs are essential...",
Author: Author{Name: "Alice", Email: "alice@example.com"},
Tags: map[string]bool{"go": true, "tutorial": true},
Draft: false,
}
data, _ := json.MarshalIndent(post, "", " ")
fmt.Println(string(data))
}
Expected JSON output:
{
"title": "Go Maps & Structs",
"content": "Maps and structs are essential...",
"author": {
"name": "Alice",
"email": "alice@example.com"
},
"tags": {
"go": true,
"tutorial": true
},
"draft": false
}
FAQ
{{< faq question="When should I use a map vs a struct?" >}} Use maps for dynamic key-value data where keys aren't known at compile time. Use structs for fixed sets of fields known at compile time (API request/response, configuration, database rows). {{< /faq >}}
{{< faq question="Are maps thread-safe in Go?" >}}
No. Concurrent reads and writes to a map cause a panic. Use sync.RWMutex or sync.Map for concurrent access.
{{< /faq >}}
{{< faq question="Can I compare structs with ==?" >}} Yes, if all fields are comparable (no slices, maps, or functions). Comparable structs can be used as map keys. {{< /faq >}}
{{< faq question="What is the difference between struct and map initialization?" >} Structs use field names or positions. Maps use key-value pairs. Structs are type-checked at compile time; maps are dynamic. {{< /faq >}}
{{< faq question="How do I access struct tags at runtime?" >}
Use the reflect package: t := reflect.TypeOf(myStruct); field, _ := t.FieldByName("MyField"); tag := field.Tag.Get("json").
{{< /faq >}}
Try It Yourself
package main
import (
"encoding/json"
"fmt"
)
type Config struct {
Host string `json:"host"`
Port int `json:"port"`
Debug bool `json:"debug"`
Timeout int `json:"timeout_secs"`
}
func main() {
config := Config{
Host: "localhost",
Port: 8080,
Debug: true,
Timeout: 30,
}
data, _ := json.MarshalIndent(config, "", " ")
fmt.Println("JSON:")
fmt.Println(string(data))
var decoded Config
json.Unmarshal(data, &decoded)
fmt.Printf("Decoded: %+v\n", decoded)
fmt.Printf("Host: %s, Port: %d\n", decoded.Host, decoded.Port)
}
What's Next
Now that you understand maps and structs, learn about pointers for passing by reference.
| Topic | Description | Link |
|---|---|---|
| Go Pointers | &, *, nil, new, pointer receivers | {{< ref "09-pointers" >}} |
| Go Methods | Receiver types, value vs pointer | {{< ref "10-methods" >}} |
| Rust Structs | Compare Rust struct patterns | Rust |