Recipe 01 / filter

Keep the rows that matter.

A practical detail export usually needs a little cleanup before it needs anything clever. Normalize a field, filter by names, parse a value only inside the predicate, then choose the output shape.

The task

Given a people.csv file, create an export containing active adults with only the fields a downstream tool needs.

id,name,status,age,notes
101, Ana ,ACTIVE,34,kept
102,Ben,paused,42,skip
103,Cleo,active,17,skip

The program

package main

import (
    "log"
    "strconv"
    "strings"

    "github.com/osvik/simplecsv"
)

func main() {
    input, err := simplecsv.ReadCsvFileE("people.csv")
    if err != nil {
        log.Fatal(err)
    }

    normalized, ok := input.MapColumnByField("status", func(value string, _ int) string {
        return strings.ToLower(strings.TrimSpace(value))
    })
    if !ok {
        log.Fatal("status field not found")
    }

    active, ok := normalized.Where("status", "active")
    if !ok {
        log.Fatal("status field not found")
    }

    adults, ok := active.FilterByField("age", func(value string) bool {
        age, err := strconv.Atoi(value)
        return err == nil && age >= 18
    })
    if !ok {
        log.Fatal("age field not found")
    }

    output, ok := adults.OnlyThisFields([]string{"id", "name", "age"})
    if !ok {
        log.Fatal("could not select output fields")
    }

    output, ok = output.SortByField("name", true)
    if !ok {
        log.Fatal("name field not found")
    }

    if err := output.WriteCsvFileE("active-adults.csv"); err != nil {
        log.Fatal(err)
    }
}

Each operation returns a new CSV, so the original input remains available if the command later needs a second output.

Choose the filter

Use Where for exact equality:

active, ok := input.Where("status", "active")

Use WhereFold when capitalization varies:

active, ok := input.WhereFold("status", "active")

Use FilterByField when the rule needs parsing or more than one condition:

kept, ok := input.FilterByField("amount", func(value string) bool {
    amount, err := strconv.ParseFloat(value, 64)
    return err == nil && amount >= 100
})

More small transforms

These compose with the same pattern:

  • FillColumnByField adds a constant default to every data cell.
  • ReplaceInField replaces exact known values without a regular expression.
  • TrimSpace trims every cell, including headers, and rejects duplicate headers created by trimming.
  • UniqueByFields("email") keeps the first row for each named key.
  • AddComputedColumn creates a field from a copy of the row map.

Back to all recipes ->