Skip to main content

CSV

The csv package provides CSV import/export capabilities using the same tabular tag system as the Excel package. It shares the same tabular.Importer and tabular.Exporter interfaces, making it easy to swap between formats.

Overview

OperationEntry PointDescription
Importcsv.NewImporterFor[T]()Parse CSV file/reader into typed struct slices
Exportcsv.NewExporterFor[T]()Write struct slices to CSV files/buffers

Model Definition

CSV uses the same tabular struct tag as Excel. See the Excel documentation for the full tag reference.

type Employee struct {
orm.FullAuditedModel `tabular:"-"`

Name string `tabular:"Name,width:20"`
Email string `tabular:"Email,width:30"`
Department string `tabular:"Department"`
JoinDate timex.Date `tabular:"Join Date,format:2006-01-02"`
Salary decimal.Decimal `tabular:"Salary"`
IsActive bool `tabular:"Active"`
}

Exporting

Basic Export

import "github.com/coldsmirk/vef-framework-go/csv"

exporter := csv.NewExporterFor[Employee]()

// Export to file
err := exporter.ExportToFile(employees, "employees.csv")

// Export to buffer (for HTTP response)
buf, err := exporter.Export(employees)

Export Options

OptionDefaultDescription
csv.WithExportDelimiter(rune),Field delimiter character
csv.WithoutWriteHeader()write headerSkip the header row
csv.WithCRLF()LF onlyUse Windows-style CRLF line endings
// TSV export with Windows line endings
exporter := csv.NewExporterFor[Employee](
csv.WithExportDelimiter('\t'),
csv.WithCRLF(),
)

// Export without header row
exporter := csv.NewExporterFor[Employee](
csv.WithoutWriteHeader(),
)

Custom Formatter

exporter := csv.NewExporterFor[Employee]()

exporter.RegisterFormatter("status", tabular.FormatterFunc(func(value any) (string, error) {
if active, ok := value.(bool); ok && active {
return "Y", nil
}
return "N", nil
}))

Importing

Basic Import

importer := csv.NewImporterFor[Employee]()

// Import from file
data, importErrors, err := importer.ImportFromFile("employees.csv")
if err != nil {
return err // Fatal error
}

// Check row-level errors
for _, e := range importErrors {
log.Printf("Row %d: %v", e.Row, e.Err)
}

employees := data.([]Employee)

Import from io.Reader

data, importErrors, err := importer.Import(reader)

Import Options

OptionDefaultDescription
csv.WithImportDelimiter(rune),Field delimiter character
csv.WithoutHeader()has headerCSV has no header row; map columns by position
csv.WithSkipRows(n)0Skip leading rows before the header
csv.WithoutTrimSpace()trim enabledDisable automatic whitespace trimming
csv.WithComment(rune)noneComment character (lines starting with this are skipped)
// TSV file with 2 title rows, comment lines starting with #
importer := csv.NewImporterFor[Employee](
csv.WithImportDelimiter('\t'),
csv.WithSkipRows(2),
csv.WithComment('#'),
)

// CSV without header (columns matched by position/order)
importer := csv.NewImporterFor[Employee](
csv.WithoutHeader(),
)

Header vs No-Header Mode

ModeColumn Mapping
With header (default)Header names → tabular tag names
Without headerColumn position → tabular field order

When using WithoutHeader(), columns are matched by position. Use the order tag attribute to control field ordering:

type Record struct {
Name string `tabular:"Name,order:0"`
Email string `tabular:"Email,order:1"`
Age int `tabular:"Age,order:2"`
}

Custom Parser

importer := csv.NewImporterFor[Employee]()

importer.RegisterParser("date", tabular.ValueParserFunc(func(cellValue string, targetType reflect.Type) (any, error) {
return time.Parse("01/02/2006", cellValue)
}))

Validation

Imported records are automatically validated using validator.Validate(...), same as the Excel importer.

Error Handling

ErrorMeaning
ErrDataMustBeSliceExport data must be a slice
ErrNoDataRowsFoundNo data rows in the file
ErrDuplicateColumnNameDuplicate column names in header
ErrFieldNotSettableStruct field cannot be set

CSV vs Excel

FeatureCSVExcel
File format.csv (plain text).xlsx (binary)
Multiple sheetsNoYes
Column widthsIgnoredApplied
DelimiterConfigurableN/A
Comment linesSupportedN/A
Trim whitespaceConfigurableN/A
Line endingsLF or CRLFN/A
DependenciesGo stdlibexcelize

Both packages implement tabular.Importer / tabular.Exporter, so you can swap between them without changing your model definitions.