Let's think about this for a second
Part 3 is the final stage of the project, where we'll put the goroutine and channel concepts from the Advanced chapter into practice. Since it's pretty useless if the task list disappears every time the program closes, we'll learn how to save/load it to a tasks.json file using the encoding/json package. On top of that, we'll set up a background goroutine running on a time.Ticker to try out a periodic auto-save pattern. By the end of this stage, the Task Manager project will be a complete mini application combining struct, methods, error handling, file I/O, and goroutines.
Let's build it for real
Write a SaveToFile(filename string) error method that uses json.MarshalIndent to turn the tasks slice into JSON bytes, then uses os.WriteFile to save it to a file. Write a LoadFromFile(filename string) error method that uses os.ReadFile to read the file and json.Unmarshal to fill the tasks slice back in. Start a goroutine that uses time.NewTicker(10 * time.Second) so that every tick from the ticker.C channel automatically calls SaveToFile in the background. Right before the program closes at the end of main(), call SaveToFile one more time to make sure no data is lost.
Code Example
package main
import (
"encoding/json"
"fmt"
"os"
"time"
)
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
}
type TaskManager struct {
tasks []Task
}
func (m *TaskManager) SaveToFile(filename string) error {
data, err := json.MarshalIndent(m.tasks, "", " ")
if err != nil {
return err
}
return os.WriteFile(filename, data, 0644)
}
func (m *TaskManager) LoadFromFile(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return err
}
return json.Unmarshal(data, &m.tasks)
}
func (m *TaskManager) startAutoSave(filename string, interval time.Duration, done chan bool) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := m.SaveToFile(filename); err != nil {
fmt.Println("auto-save error:", err)
} else {
fmt.Println("auto-saved to", filename)
}
case <-done:
return
}
}
}
func main() {
manager := &TaskManager{}
_ = manager.LoadFromFile("tasks.json")
manager.tasks = append(manager.tasks, Task{ID: 1, Title: "Finish project"})
done := make(chan bool)
go manager.startAutoSave("tasks.json", 10*time.Second, done)
// program logic runs here...
time.Sleep(2 * time.Second)
done <- true
if err := manager.SaveToFile("tasks.json"); err != nil {
fmt.Println("final save error:", err)
}
fmt.Println("Task Manager project complete!")
}The task list gets saved into the tasks.json file as formatted JSON, and the data isn't lost even after the program closes.Try it in 5 minutes
Within 5 minutes, change the auto-save interval from 10 seconds to 2 seconds, then open the tasks.json file in an editor and observe how its content keeps updating.
One quick word of caution
In a production app, a crash mid-write can corrupt your data, so it's better to use the pattern of writing to a temp file first and then swapping it in atomically with os.Rename.