03 — Slices, maps, structs, and JSON
Goal
Represent tasks as Go data and convert them to JSON for a small command-line preview.
Step 1: Define a struct
Replace main.go:
package main
import (
"encoding/json"
"fmt"
)
type Task struct {
ID string `json:"id"`
Title string `json:"title"`
Status string `json:"status"`
}
func main() {
tasks := []Task{
{ID: "task-1", Title: "Sketch the API", Status: "TODO"},
{ID: "task-2", Title: "Build the screen", Status: "IN_PROGRESS"},
}
byID := make(map[string]Task)
for _, task := range tasks {
byID[task.ID] = task
}
selected, ok := byID["task-1"]
if !ok {
panic("task not found")
}
data, err := json.MarshalIndent(selected, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(data))
}
Run gofmt -w main.go and go run .. Expected output is a formatted JSON object with id, title, and status.
Step 2: Understand the containers
A slice such as []Task is a growable sequence. append(tasks, item) returns the resulting slice. A map such as map[string]Task stores values by key. Map lookup returns a value and an ok flag; use the flag to distinguish a missing key from a zero-valued value. A struct groups related fields. JSON tags define wire names, and exported fields (capitalized names) are encoded by encoding/json.
Slices and maps are reference-like descriptors over underlying storage: copying one does not necessarily copy all its elements. That makes them efficient, but means two parts of a program can observe shared changes. When you need independent data, copy the slice or individual values deliberately. A map is not safe for simultaneous writes from multiple goroutines; later the in-memory repository protects its map with a mutex.
JSON is a text encoding often used by web APIs. gRPC in this course sends Protocol Buffer messages instead, but the same struct-design ideas apply: choose clear fields, keep identifiers stable, and distinguish missing data from meaningful zero values.
Step 3: Add a task
Before marshaling, append:
tasks = append(tasks, Task{ID: "task-3", Title: "Try the app", Status: "TODO"})
Rebuild byID after appending, then look up task-3 and print it. Observe that maps are not automatically kept in sync with the slice.
Checkpoint
Add a Description string field with JSON tag description. Create a task with a description and confirm it appears in JSON.
Practice
Write a function that returns all tasks whose status is TODO. Return a slice and leave the original slice unchanged.
Troubleshooting
If JSON prints {}, check that struct field names start with capitals. If lookup returns a zero task, check the ok result and the key spelling.