csvamp

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2025 License: Apache-2.0 Imports: 9 Imported by: 0

README

CSVAMP

GoDoc Latest Version Go Report Card

Read CSVs directly into structs.


Features

  • Minimal reflection use
    • Field reflection only at mapper create time
    • Efficient field setters at read time
  • Support for common field types: bool,int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,float32,float64,string
    • and pointers to those types
    • quoted detection on string pointers
  • Support for additional types - when they implement csvamp.CsvUnmarshaler, csvamp.CsvQuotedUnmarshaler or encoding.TextUnmarshaler
  • Support for embedded structs and nested structs
  • Map struct fields to CSV field index or header name (using csv tag)
  • Adaptable to varying CSVs
  • Post processor option for validating and/or finalising struct
  • Optional error handler for tracking errors without halting reads

Installation

go get github.com/go-andiamo/csvamp

Examples

1. Basic implied field ordering

Without specifying any csv tags on struct fields, the order of the struct fields implies the CSV field order...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       int
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`

    r := mapper.Reader(strings.NewReader(data), nil)
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


2. Explicit field indexes

You can specify the actual CSV field indexes using the csv tag...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    Age       int    `csv:"[3]"`
    LastName  string `csv:"[2]"`
    FirstName string `csv:"[1]"`
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`

    r := mapper.Reader(strings.NewReader(data), nil)
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


3. Mapping to CSV headers

You can use the csv tag to map struct fields to explicit CSV headers...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    Age       int    `csv:"Age"`
    LastName  string `csv:"Last name"`
    FirstName string `csv:"First name"`
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`

    r := mapper.Reader(strings.NewReader(data), nil)
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


4. Capturing the CSV line number

You can use a special csv tag to capture the CSV line number into the struct...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    Line      int `csv:"[line]"`
    FirstName string
    LastName  string
    Age       int
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`

    r := mapper.Reader(strings.NewReader(data), nil)
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


5. Capturing the CSV record

You can use a special csv tag to capture the CSV record (line) into the struct...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    Raw       []string `csv:"[raw]"`
    FirstName string
    LastName  string
    Age       int
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`

    r := mapper.Reader(strings.NewReader(data), nil)
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


6. Adapting the mapper to varying CSVs

Sometimes, your CSV won't always arrive in the format you're expecting - the mapper can adapt to that...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       int
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const sample1 = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`
    const sample2 = `Age,First name,Last name
50,Frodo,Baggins
38,Samwise,Gamgee
87,Aragorn,Elessar
2931,Legolas,Greenleaf
24000,Gandalf,The Grey`

    recs, err := mapper.Reader(strings.NewReader(sample1), nil).ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)

    m2, err := mapper.Adapt(false, csvamp.OverrideMappings{
        {
            FieldName:    "FirstName",
            CsvFieldName: "First name",
        },
        {
            FieldName:    "LastName",
            CsvFieldName: "Last name",
        },
        {
            FieldName:    "Age",
            CsvFieldName: "Age",
        },
    })
    if err != nil {
        panic(err)
    }
    recs, err = m2.Reader(strings.NewReader(sample2), nil).ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


7. Using postProcessor to validate

You can use the postProcessor to validate records...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    Line      int `csv:"[line]"`
    FirstName string
    LastName  string
    Age       int
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`

    r := mapper.Reader(strings.NewReader(data), func(row *Record) error {
        if row.Age > 200 {
            return fmt.Errorf("age is too high - on line %d", row.Line)
        }
        return nil
    })
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


8. Using postProcessor to adjust struct read

Sometimes, some fields are calculated - you can exclude them from being read using csv:"-" and then fill them in using a postProcessor...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       int
    AgeInDays int `csv:"-"`
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`

    r := mapper.Reader(strings.NewReader(data), func(row *Record) error {
        row.AgeInDays = row.Age * 365
        return nil
    })
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


9. Reading one row at a time
package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "io"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       int
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`

    r := mapper.Reader(strings.NewReader(data), nil)

    for {
        record, err := r.Read()
        if err == io.EOF {
            break
        } else if err != nil {
            panic(err)
        }
        fmt.Printf("%+v\n", record)
}
}

try on go-playground


10. Iterating over rows
package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       int
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`

    r := mapper.Reader(strings.NewReader(data), nil)

    err := r.Iterate(func(record Record) (bool, error) {
        fmt.Printf("%+v\n", record)
        return true, nil
    })
    if err != nil {
        panic(err)
    }
}

try on go-playground


11. Using CsvUnmarshaler (e.g. dates)

CSVs come in all flavours - and dates (which csvamp doesn't handle natively) come in varying formats. Use types that implement csvamp.CsvUnmarshaler to resolve this...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
    "time"
)

type DOB time.Time

func (d *DOB) UnmarshalCSV(val string, record []string) error {
    dt, err := time.Parse("2006-01-02", val)
    if err != nil {
        return err
    }
    *d = DOB(dt)
    return nil
}

type Record struct {
    FirstName string
    LastName  string
    DOB       DOB
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Date Of Birth
Frodo,Baggins,2968-09-22
Samwise,Gamgee,2980-04-06
Aragorn,Elessar,2931-03-01`

    r := mapper.Reader(strings.NewReader(data), nil)
    err := r.Iterate(func(record Record) (bool, error) {
        fmt.Printf("Name: %s %s\n", record.FirstName, record.LastName)
        fmt.Printf(" DOB: %s\n", time.Time(record.DOB).Format("Mon, 02 Jan 2006"))
        return true, nil
    })
    if err != nil {
        panic(err)
    }
}

try on go-playground


12. Utilising encoding.TextUnmarshaler (e.g. dates)

csvamp only supports 'primitive' types - but, fortunately, many additional types support the encoding.TextUnmarshaler interface - this can be utilised. The following example utilises the fact that time.Time implements the encoding.TextUnmarshaler interface...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
    "time"
)

type Record struct {
    FirstName string
    LastName  string
    DOB       time.Time
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Date Of Birth
Frodo,Baggins,2968-09-22T00:00:00Z
Samwise,Gamgee,2980-04-06T00:00:00Z
Aragorn,Elessar,2931-03-01T00:00:00Z`

    r := mapper.Reader(strings.NewReader(data), nil)
    err := r.Iterate(func(record Record) (bool, error) {
        fmt.Printf("Name: %s %s\n", record.FirstName, record.LastName)
        fmt.Printf(" DOB: %s\n", time.Time(record.DOB).Format("Mon, 02 Jan 2006"))
        return true, nil
    })
    if err != nil {
        panic(err)
    }
}

try on go-playground


13. Optional fields? Use pointer types

When a struct field is a pointer type, csvamp treats it as optional - if the corresponding CSV field is empty - it is treated as not there at all...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       *int
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,
Gandalf,The Grey,`

    r := mapper.Reader(strings.NewReader(data), nil)
    err := r.Iterate(func(record Record) (bool, error) {
        fmt.Printf("Name: %s %s\n", record.FirstName, record.LastName)
        if record.Age != nil {
            fmt.Printf("Age: %d\n", *record.Age)
        } else {
            fmt.Printf("Age: %s\n", "(unknown)")
        }
        return true, nil
    })
    if err != nil {
        panic(err)
    }
}

try on go-playground


14. Using error handler to track errors

When using ReadAll() (or Iterate()) you may not want to halt when an error is encountered...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       int
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,not a number!
Samwise,Gamgee,38
Aragorn,Elessar,not a number!
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`

    eh := &errorHandler{}
    r := mapper.Reader(strings.NewReader(data), nil).WithErrorHandler(eh)
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)

    fmt.Printf("Found %d errors\n", len(eh.errs))
    for i, err := range eh.errs {
        fmt.Printf("Line %d: %s\n", eh.lines[i], err)
    }
}

type errorHandler struct {
    errs  []error
    lines []int
}

func (eh *errorHandler) Handle(err error, line int) error {
    eh.errs = append(eh.errs, err)
    eh.lines = append(eh.lines, line)
    return nil
}

try on go-playground


15. Manually supply CSV headers

Sometimes, incoming CSV won't have a header line - but your struct fields are mapped to header names. This can be handled by supplying the headers...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "github.com/go-andiamo/csvamp/csv"
    "strings"
)

type Record struct {
    Age       int    `csv:"Age"`
    LastName  string `csv:"Last name"`
    FirstName string `csv:"First name"`
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`

    // csv.NoHeader(true) indicates to the reader that the CSV has no header line...
    r := mapper.Reader(strings.NewReader(data), nil, csv.NoHeader(true)).
        SupplyHeaders([]string{"First name", "Last name", "Age"})
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


16. Quoted detection on string pointer fields

Using string pointer fields determines whether the CSV field was quoted or un-quoted for empty string or nil...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    Name *string
    Age  int
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `Name,Age
"",50
,38`

    r := mapper.Reader(strings.NewReader(data), nil)
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    // Name for first record should be pointer to empty string
    // Name for second record should be nil
    fmt.Printf("%+v\n", recs)
}

try on go-playground


17. Nested structs
package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       int
    Address   Address
}

type Address struct {
    Street string
    Town   string
    County string
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age,Street,Town,County
Frodo,Baggins,50,1 Bagshot Row,Hobbiton,The Shire
Samwise,Gamgee,38,2 Bagshot Row,Hobbiton,The Shire
Aragorn,Elessar,87,Royal Quarters,The Citadel,Minas Tirith`

    r := mapper.Reader(strings.NewReader(data), nil)
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


18. Embedded structs
package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       int
    Address
}

type Address struct {
    Street string
    Town   string
    County string
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age,Street,Town,County
Frodo,Baggins,50,1 Bagshot Row,Hobbiton,The Shire
Samwise,Gamgee,38,2 Bagshot Row,Hobbiton,The Shire
Aragorn,Elessar,87,Royal Quarters,The Citadel,Minas Tirith`

    r := mapper.Reader(strings.NewReader(data), nil)
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


19. Nested structs with unmarshalling

Sometimes, you may want a nested struct to dissect a single csv field. If the nested struct implements CsvUnmarshaler, CsvQuotedUnmarshaler or encoding.TextUnmarshaler interface, the struct field is treated as mapped to a single csv field...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       int
    Address   Address
}

type Address struct {
    Lines []string
}

func (a *Address) UnmarshalCSV(val string, record []string) error {
    if val != "" {
        a.Lines = strings.Split(val, "\n")
    }
    return nil
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age,Address
Frodo,Baggins,50,"1 Bagshot Row
Hobbiton
The Shire"
Samwise,Gamgee,38,"2 Bagshot Row
Hobbiton
The Shire"
Aragorn,Elessar,87,"Royal Quarters
The Citadel
Minas Tirith"`

    r := mapper.Reader(strings.NewReader(data), nil)
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


20. Special handling of []string fields

csvamp has special handling of []string fields - it treats the quoted csv field as comma separated...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       int
    Address   []string
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age,Address
Frodo,Baggins,50,"1 Bagshot Row,Hobbiton,The Shire"
Samwise,Gamgee,38,"2 Bagshot Row,Hobbiton,The Shire"
Aragorn,Elessar,87,"Royal Quarters,The Citadel,Minas Tirith"`

    r := mapper.Reader(strings.NewReader(data), nil)
    recs, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", recs)
}

try on go-playground


21. Range over Iterator()

The reader provides an Iterator() that can be used to range over...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       int
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,2931
Gandalf,The Grey,24000`

    r := mapper.Reader(strings.NewReader(data), nil)

    rows := make([]Record, 0)
    for _, row := range r.Iterator() {
        rows = append(rows, row)
    }
    fmt.Printf("%+v\n", rows)
}

22. Range over Iterator() - with error handler

By default, ranging over Iterator() will stop when an error is encountered - but by using an ErrorHandler, the ranging can continue whilst capturing errors...

package main

import (
    "fmt"
    "github.com/go-andiamo/csvamp"
    "strings"
)

type Record struct {
    FirstName string
    LastName  string
    Age       int
}

var mapper = csvamp.MustNewMapper[Record]()

func main() {
    const data = `First name,Last name,Age
Frodo,Baggins,50
Samwise,Gamgee,38
Aragorn,Elessar,87
Legolas,Greenleaf,not a number
Gandalf,The Grey,not a number`

    eh := &errorHandler{}
    r := mapper.Reader(strings.NewReader(data), nil).WithErrorHandler(eh)

    rows := make([]Record, 0)
    for _, row := range r.Iterator() {
        rows = append(rows, row)
    }
    fmt.Printf("Rows: %+v\n", rows)
    fmt.Printf("Errors: %+v\n", eh)
}

type errorHandler struct {
    errs  []string
    lines []int
}

func (eh *errorHandler) Handle(err error, line int) error {
    eh.errs = append(eh.errs, err.Error())
    eh.lines = append(eh.lines, line)
    return nil
}

Documentation

Overview

Package csvamp - Go package for reading CSV directly into structs

Example:

	type MyStruct struct {
		Line int      `csv:"[line]"` // optionally capture the csv line number
		Raw  []string `csv:"[raw]"`  // optionally capture the csv record
		Foo  string   `csv:"[1]"`    // map to indexed csv field (1 based)
		Bar  string   `csv:"bar"`    // map to named csv field
	}

	// create the mapper...
	m, err := csvamp.NewMapper[MyStruct]()
	if err != nil {
		log.Fatal(err)
	}

	const sample = `foo,bar
Aaa,Bbb
Ccc,Ddd`

	result, err := m.Reader(strings.NewReader(sample), nil).ReadAll()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%+v\n", result)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CsvQuotedUnmarshaler added in v1.2.0

type CsvQuotedUnmarshaler interface {
	UnmarshalQuotedCSV(val string, quoted bool, record []string) error
}

CsvQuotedUnmarshaler is the interface implemented by an object that can unmarshal a string CSV field representation of itself.

CsvQuotedUnmarshaler is similar to CsvUnmarshaler, except that it informs whether the value string (which may be empty) was quoted

type CsvUnmarshaler

type CsvUnmarshaler interface {
	UnmarshalCSV(val string, record []string) error
}

CsvUnmarshaler is the interface implemented by an object that can unmarshal a string CSV field representation of itself.

This is effectively the same as encoding.TextUnmarshaler, but provides the field as a string along with the entire record

type DefaultEmptyValues added in v1.2.0

type DefaultEmptyValues bool

DefaultEmptyValues is an option that can be passed to NewMapper / MustNewMapper

if set to true, when reading, empty fields in the CSV are treated as zero values for types bool, int, uint and float

By default, reading empty CSV fields into bool, int, uint & float will cause an error

type ErrorHandler added in v1.1.0

type ErrorHandler interface {
	// Handle handles the error - if it returns the error, then further processing stops
	Handle(err error, line int) error
}

ErrorHandler is an interface that can be used with ReaderContext.WithErrorHandler

type IgnoreUnknownFieldNames

type IgnoreUnknownFieldNames bool

IgnoreUnknownFieldNames is an option that can be passed to NewMapper / MustNewMapper

if set to true, the mapper will ignore references to unknown CSV field (header) names

By default, if a struct field references (via tag) an unknown CSV field (header) name - it will error when reading

type Mapper

type Mapper[T any] interface {
	// Reader returns a reader context for the mapper using the provided io.Reader
	//
	// the postProcessor func, if provided, can be used to validate (or modify) the struct after it has been read
	//
	// the options can be any of csv.Comma, csv.Comment, csv.FieldsPerRecord, csv.LazyQuotes, csv.TrimLeadingSpace or csv.NoHeader
	Reader(r io.Reader, postProcessor func(row *T) error, options ...any) ReaderContext[T]
	// ReaderContext returns a reader context for the mapper using the provided csv.Reader
	//
	// the postProcessor func, if provided, can be used to validate (or modify) the struct after it has been read
	ReaderContext(r *csv.Reader, postProcessor func(row *T) error) ReaderContext[T]
	// Adapt creates a new Mapper from this mapper with struct field to CSV fields overridden
	//
	// Options from the original mapper are preserved unless overridden by the provided options
	Adapt(clear bool, mappings OverrideMappings, options ...any) (Mapper[T], error)
	// Mappings returns the current effective struct field to CSV field mappings
	Mappings() OverrideMappings
}

Mapper is an interface for mapping structs onto CSV

Use NewMapper / MustNewMapper to create a new Mapper

func MustNewMapper

func MustNewMapper[T any](options ...any) Mapper[T]

MustNewMapper is the same as NewMapper - except that it panics in case of error

func NewMapper

func NewMapper[T any](options ...any) (Mapper[T], error)

NewMapper creates a new struct to CSV Mapper for the specified generic struct type

type OverrideMapping

type OverrideMapping struct {
	// FieldName is the name of the field in the original struct
	FieldName string
	// CsvFieldIndex is the field index in the CSV
	//
	// If this value is 0 (zero), the index is ignored and CsvFieldName is used instead
	//
	// If this value is negative, the index mapping is removed
	CsvFieldIndex int
	// CsvFieldName is the field name (header) in the CSV
	//
	// If this value is empty, the name is ignored and CsvFieldIndex is used instead
	//
	// If this value starts with a "-", the named mapping is removed
	CsvFieldName string
}

type OverrideMappings

type OverrideMappings []OverrideMapping

OverrideMappings is passed to Mapper.Adapt to override field to CSV mappings

type ReaderContext

type ReaderContext[T any] interface {
	// Read reads the next CSV line as a struct or returns error io.EOF
	Read() (T, error)
	// ReadAll reads all CSV lines as structs
	ReadAll() ([]T, error)
	// Iterate iterates over CSV lines and calls the provided function with the read struct
	//
	// Iteration continues until the end of the CSV or when the provided function returns false or an error
	Iterate(fn func(T) (bool, error)) error
	// Iterator returns an iterator that can be ranged over
	//
	// range iteration stops when an error is encountered - unless an ErrorHandler is provided using WithErrorHandler
	Iterator() func(func(int, T) bool)
	// WithErrorHandler sets the error handler - which can be used to track errors during ReadAll and Iterate
	//
	// Setting an error handler means that errors are reported but don't necessarily halt further reading
	WithErrorHandler(eh ErrorHandler) ReaderContext[T]
	// SupplyHeaders enables CSV headers to be manually supplied
	//
	// Sometimes your csv may not have headers, or you may have already read (and normalised) them
	SupplyHeaders(headers []string) ReaderContext[T]
}

ReaderContext is the interface used to actually read structs from CSV

A reader context is obtained from Mapper.Reader or Mapper.ReaderContext

type ReaderError added in v1.2.0

type ReaderError struct {
	Line int
	Err  error
}

ReaderError is the wrapped error returned from ReaderContext.ReadAll / ReaderContext.Iterate

func (*ReaderError) Error added in v1.2.0

func (e *ReaderError) Error() string

func (*ReaderError) Unwrap added in v1.2.0

func (e *ReaderError) Unwrap() error

Directories

Path Synopsis
_examples
Package csv - Go package to replace stdlib encoding/csv reader - with more exposed info
Package csv - Go package to replace stdlib encoding/csv reader - with more exposed info

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL