3 · Reliability and project · 45 MIN
Project · Validate and summarize expenses
Combine validated input, grouping and deterministic reporting.
This project accepts a small JSON array and groups nonnegative whole-unit expenses. Decoding establishes syntax; validation establishes domain rules; sorting makes output stable. The example stops at the first invalid record rather than publishing a partial report. A production importer must also bound request bytes, numeric ranges and record counts. Choose the contract before optimizing: accepting a refund, for example, changes the meaning of the negative-amount check and requires new tests.
Keep report pure enough to test without invoking main.
Read the example
package main
import ("encoding/json"; "fmt"; "sort")
type Expense struct { Category string; Amount int }
func report(raw []byte) (map[string]int, error) {
var rows []Expense
if err := json.Unmarshal(raw, &rows); err != nil { return nil, err }
totals := make(map[string]int)
for _, row := range rows {
if row.Category == "" || row.Amount < 0 { return nil, fmt.Errorf("invalid expense") }
totals[row.Category] += row.Amount
}
return totals, nil
}
func main() {
totals, err := report([]byte(`[{"Category":"food","Amount":5},{"Category":"food","Amount":7}]`))
if err != nil { fmt.Println("invalid"); return }
keys := make([]string,0,len(totals)); for key := range totals { keys=append(keys,key) }; sort.Strings(keys)
for _, key := range keys { fmt.Println(key, totals[key]) }
}Check the expected output
food 12
Your challenge
Create table-driven tests in main_test.go and extend the importer to reject unknown JSON fields, oversized input and overflowing totals.
Solution cost: O(n + k log k) including sorted output for k categories. time · Account for collection storage separately from the returned result. space
Common trap
Valid JSON alone does not establish a safe business record.
Study the project implementation
package main
import ("encoding/json"; "fmt"; "sort")
type Expense struct { Category string; Amount int }
func report(raw []byte) (map[string]int, error) {
var rows []Expense
if err := json.Unmarshal(raw, &rows); err != nil { return nil, err }
totals := make(map[string]int)
for _, row := range rows {
if row.Category == "" || row.Amount < 0 { return nil, fmt.Errorf("invalid expense") }
totals[row.Category] += row.Amount
}
return totals, nil
}
func main() {
totals, err := report([]byte(`[{"Category":"food","Amount":5},{"Category":"food","Amount":7}]`))
if err != nil { fmt.Println("invalid"); return }
keys := make([]string,0,len(totals)); for key := range totals { keys=append(keys,key) }; sort.Strings(keys)
for _, key := range keys { fmt.Println(key, totals[key]) }
}Further reading: Official documentation