🎓 A1Learner
🔍
0 of 34 lessons completed

Go Project: Task Tracker

The Task Tracker is a small program that keeps a list of jobs and reports the jobs still open. It joins structs, methods, generics, JSON, and file saving in one program.

A Task holds a title and a done flag

The Tasks type names a slice of Task values. The program builds three tasks and prints each title with its flag.

package main

import "fmt"

type Task struct {
	Title string
	Done  bool
}

type Tasks []Task

func main() {
	tasks := Tasks{
		{Title: "Write report", Done: true},
		{Title: "Book flights"},
		{Title: "Water plants"},
	}
	for _, task := range tasks {
		fmt.Println(task.Title, task.Done)
	}
}

A method marks a task done

The Complete method belongs to the Tasks type. It finds a task by its title and sets the Done flag of that task.

package main

import "fmt"

type Task struct {
	Title string
	Done  bool
}

type Tasks []Task

func (t Tasks) Complete(title string) {
	for i := range t {
		if t[i].Title == title {
			t[i].Done = true
		}
	}
}

func main() {
	tasks := Tasks{
		{Title: "Write report"},
		{Title: "Book flights"},
	}
	fmt.Println(tasks[0].Done)
	tasks.Complete("Write report")
	fmt.Println(tasks[0].Done)
}

A generic filter keeps the open tasks

The filter helper works for a slice of any type. A check function decides which values the helper keeps.

package main

import "fmt"

type Task struct {
	Title string
	Done  bool
}

type Tasks []Task

func filter[T any](items []T, keep func(T) bool) []T {
	var kept []T
	for _, item := range items {
		if keep(item) {
			kept = append(kept, item)
		}
	}
	return kept
}

func main() {
	tasks := Tasks{
		{Title: "Write report", Done: true},
		{Title: "Book flights"},
		{Title: "Water plants"},
	}
	open := filter(tasks, func(task Task) bool {
		return !task.Done
	})
	for _, task := range open {
		fmt.Println(task.Title)
	}
}

Tasks save to a JSON file and load back

Marshal turns the tasks into JSON bytes. WriteFile saves the bytes, and ReadFile with Unmarshal brings the tasks back.

package main

import (
	"encoding/json"
	"fmt"
	"os"
)

type Task struct {
	Title string `json:"title"`
	Done  bool   `json:"done"`
}

type Tasks []Task

func main() {
	tasks := Tasks{
		{Title: "Write report", Done: true},
		{Title: "Book flights"},
	}
	data, err := json.Marshal(tasks)
	if err != nil {
		fmt.Println("Save failed")
		return
	}
	if err := os.WriteFile("tasks.json", data, 0644); err != nil {
		fmt.Println("Save failed")
		return
	}
	saved, err := os.ReadFile("tasks.json")
	if err != nil {
		fmt.Println("Load failed")
		return
	}
	var back Tasks
	if err := json.Unmarshal(saved, &back); err != nil {
		fmt.Println("Load failed")
		return
	}
	fmt.Println(len(back))
	for _, task := range back {
		fmt.Println(task.Title, task.Done)
	}
}

Example

package main

import (
	"cmp"
	"encoding/json"
	"fmt"
	"os"
	"slices"
)

type Task struct {
	Title string `json:"title"`
	Done  bool   `json:"done"`
}

type Tasks []Task

func (t Tasks) Complete(title string) {
	for i := range t {
		if t[i].Title == title {
			t[i].Done = true
		}
	}
}

func filter[T any](items []T, keep func(T) bool) []T {
	var kept []T
	for _, item := range items {
		if keep(item) {
			kept = append(kept, item)
		}
	}
	return kept
}

func main() {
	tasks := Tasks{
		{Title: "Write report"},
		{Title: "Book flights"},
		{Title: "Water plants"},
		{Title: "Fix bike", Done: true},
	}
	tasks.Complete("Book flights")
	data, err := json.Marshal(tasks)
	if err != nil {
		fmt.Println("Save failed")
		return
	}
	if err := os.WriteFile("tasks.json", data, 0644); err != nil {
		fmt.Println("Save failed")
		return
	}
	saved, err := os.ReadFile("tasks.json")
	if err != nil {
		fmt.Println("Load failed")
		return
	}
	var loaded Tasks
	if err := json.Unmarshal(saved, &loaded); err != nil {
		fmt.Println("Load failed")
		return
	}
	open := filter(loaded, func(task Task) bool {
		return !task.Done
	})
	slices.SortFunc(open, func(a, b Task) int {
		return cmp.Compare(a.Title, b.Title)
	})
	for _, task := range open {
		fmt.Println(task.Title)
	}
	fmt.Println("Open:", len(open))
}

Key points

  • A Task value holds a Title and a Done flag.
  • The Tasks type is a slice of Task values with its own methods.
  • The Complete method marks one task as done by its title.
  • The generic filter helper keeps only the values its check accepts.
  • json.Marshal turns the tasks into JSON bytes for saving.
  • os.WriteFile saves the bytes and os.ReadFile loads them back.
  • Sorting the open tasks by title fixes the printed order.

In this example, the program builds four tasks in a Tasks slice. The Complete method marks Book flights as done. Marshal turns the slice into JSON bytes, and WriteFile saves them in tasks.json. ReadFile loads the bytes back, and Unmarshal restores the tasks. The filter helper keeps the tasks that are not done. SortFunc orders the open tasks by title with cmp.Compare. The loop prints each open title, and the last line prints the open count.

Note

The tracker saves every task, and the open list is built after loading.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Go Playground

Exercise

Which helper keeps only the open tasks in the tracker?