excelio

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Jan 30, 2026 License: MIT Imports: 9 Imported by: 0

README

excelio

The missing link between Excel and Go structs.

Stop wrestling with cell coordinates and manual type conversions. excelio lets you map Excel rows directly to Go structs using simple tags—and handles millions of rows without breaking a sweat.

type Product struct {
    Code   string    `excel:"Code"`
    Name   string    `col:"2"`
    Price  float64   `excelcol:"C"`
    Active bool      `excel:"Active"`
    Since  time.Time `excel:"Since" fmt:"2006-01-02"`
}

products, errs, _ := excelio.ReadFile[Product]("products.xlsx")

That's it. No cell references. No type casting. Just data.


Installation

go get github.com/dreamph/excelio

Why excelio?

Problem excelio Solution
"I have a 2M row Excel file" Stream mode—constant memory, process row by row
"Column positions keep changing" Map by header name: excel:"Product Code"
"I need to validate data" Built-in go-playground/validator support
"Users need to see what's wrong" Write errors back into the Excel file
"I'm getting type conversion errors" Automatic conversion for all common types

Three Ways to Map Columns

type Product struct {
    Code  string `excel:"Code"`     // by header text
    Name  string `col:"2"`          // by column number (1-based)
    Price string `excelcol:"C"`     // by Excel letter
}

Mix and match as needed. Header-based mapping is most resilient to column reordering.


Reading Excel Files

Simple Read (Load All)
products, rowErrs, err := excelio.ReadFile[Product](
    "products.xlsx",
    excelio.Sheet("Products"),
    excelio.Header(1),
    excelio.StartRow(2),
)
From io.Reader (HTTP Upload)
func handleUpload(w http.ResponseWriter, r *http.Request) {
    file, _, _ := r.FormFile("excel")
    products, rowErrs, err := excelio.Read[Product](file)
    // process products...
}
Stream Read (Millions of Rows)

Process one row at a time—memory stays flat regardless of file size.

rowErrs, err := excelio.StreamFile[Product](
    "products.xlsx",
    excelio.Sheet("Products"),
    excelio.Header(1),
    excelio.StartRow(2),
    excelio.OnStreamRow(func(rowIdx, logicalIdx int, p *Product, rowErrs []RowError) error {
        if len(rowErrs) > 0 {
            log.Printf("Row %d failed: %v", rowIdx, rowErrs)
            return nil // continue processing
        }

        // Insert to DB, send to queue, etc.
        db.Insert(p)
        return nil
    }),
)

Writing Excel Files

Simple Write
err := excelio.WriteFile("output.xlsx", products,
    excelio.Sheet("Products"),
    excelio.Header(1),
    excelio.StartRow(2),
)
HTTP Response
func handleExport(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    w.Header().Set("Content-Disposition", `attachment; filename="export.xlsx"`)
    excelio.Write(w, products)
}
Stream Write (Big Data)

Generate massive files without memory pressure.

sw, _ := excelio.NewStreamWriterFile[Product]("big.xlsx",
    excelio.Sheet("Products"),
    excelio.Header(1),
    excelio.StartRow(2),
)
defer sw.Close()

for _, p := range millionsOfProducts {
    sw.WriteRow(&p)
}

Validation

Integrates with go-playground/validator out of the box.

type Product struct {
    Code  string  `excel:"Code"  validate:"required"`
    Price float64 `excel:"Price" validate:"required,gt=0"`
    Email string  `excel:"Email" validate:"email"`
}

validate := validator.New()
products, rowErrs, err := excelio.ReadFile[Product](
    "products.xlsx",
    excelio.UseValidator(validate),
)

// rowErrs contains field-aware validation errors
for _, e := range rowErrs {
    fmt.Printf("Row %d, Column %s (%s): %v\n",
        e.ExcelRowIndex, e.ColLetter, e.Field, e.Err)
}

Error Write-Back

Write error messages directly into the Excel file for users to review and fix.

// Read with validation
products, rowErrs, _ := excelio.ReadFile[Product]("input.xlsx")

// Write errors back to column J
excelio.WriteErrors("input.xlsx", rowErrs, excelio.ErrCol(10))

Or create a new file with errors:

excelio.WriteErrorsTo(w, inputReader, rowErrs, excelio.ErrCol(10))

Type Conversion

Automatic conversion for:

Go Type Supported Formats
string As-is
int, int8...int64 Numeric strings
uint, uint8...uint64 Numeric strings
float32, float64 Numeric strings
bool true/false, yes/no, 1/0, on/off, t/f, y/n
time.Time RFC3339, common formats, Excel serial dates
*T (pointers) Empty = nil, otherwise converted

Custom time formats via the fmt tag:

type Record struct {
    Created time.Time `excel:"Created" fmt:"02/01/2006"`
}

RowError Structure

Every error includes full context for debugging or user feedback:

type RowError struct {
    ExcelRowIndex int    // Physical row (1-based)
    LogicalIndex  int    // Data row index (excludes header)
    ColIndex      int    // Column number (1-based)
    ColLetter     string // "A", "B", "C"...
    Field         string // Struct field name
    Column        string // Header text
    Value         string // Raw cell value
    Err           error  // The actual error
}

Options Reference

Option Description
Sheet("Name") Select sheet by name
SheetAt(0) Select sheet by index (0-based)
Header(1) Header row number (1-based)
StartRow(2) First data row (1-based)
ErrCol(10) Column for error write-back (1-based)
UseValidator(v) Enable go-playground/validator
OnStreamRow(fn) Streaming row handler

Performance

  • Metadata caching — struct tags parsed once per type
  • Streaming I/O — process any file size with constant memory
  • Zero reflection per row — field mapping resolved at initialization
  • Reusable row buffers — minimal allocations during writes

Credits

Built on top of:


License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Write

func Write[T any](w io.Writer, rows []T, opts ...Option) error

Write writes a slice of structs as Excel rows into an io.Writer using streaming. This is ideal for HTTP handlers or any case where you want to stream out the XLSX file directly without touching disk.

func WriteErrors

func WriteErrors(path string, errs []RowError, opts ...Option) error

WriteErrors writes error messages into an existing Excel file identified by path. It uses ErrCol(...) to determine which column to write to.

func WriteErrorsTo

func WriteErrorsTo(w io.Writer, r io.Reader, errs []RowError, opts ...Option) error

WriteErrorsTo writes error messages into a copy of the Excel file read from r, and writes the resulting file to w. This is useful for HTTP responses or streaming to cloud storage without touching the original file.

If errs is empty, it simply copies the input stream r to w.

func WriteFile

func WriteFile[T any](path string, rows []T, opts ...Option) error

WriteFile writes a slice of structs as Excel rows into a file path using streaming. For huge datasets this is memory-friendly because it does not materialize all rows inside excelize's in-memory structures.

Types

type GenericRowHandler

type GenericRowHandler func(rowIdx, logicalIdx int, obj any, rowErrs []RowError) error

GenericRowHandler is an internal, type-erased handler stored in Options.

type Option

type Option func(*Options)

Option is the configuration option type for Read/Stream/Write APIs.

func ErrCol

func ErrCol(idx int) Option

ErrCol sets the 1-based error column index.

func Header(row int) Option

Header sets the header row index (1-based). If FirstDataRow is not set, it's automatically set to header+1.

func OnStreamRow

func OnStreamRow[T any](h RowHandler[T]) Option

OnStreamRow registers a per-row handler for Stream / StreamFile. This is required for streaming APIs; if omitted, Stream/StreamFile will return an error.

func Sheet

func Sheet(name string) Option

Sheet selects a sheet by name.

func SheetAt

func SheetAt(idx int) Option

SheetAt selects a sheet by index (0-based).

func StartRow

func StartRow(row int) Option

StartRow sets the first data row index (1-based).

func UseValidator

func UseValidator(v *validator.Validate) Option

UseValidator sets the go-playground/validator instance used for struct validation.

type Options

type Options struct {
	// Sheet selection:
	SheetName  string // If empty, SheetIndex is used
	SheetIndex int    // 0-based index; used if SheetName is empty

	// Row layout:
	HeaderRow    int // Header row index (1-based). 0 = no header
	FirstDataRow int // First data row index (1-based)

	// Row index mapper:
	//   If not nil, logical index = RowIndexMapper(ExcelRowIndex, dataIdx)
	//   Otherwise, logical index = dataIdx (1-based count of non-empty data rows).
	RowIndexMapper func(excelRow int, dataIdx int) int

	// Validation:
	GoValidator *validator.Validate

	// Error column:
	//   If > 0, WriteErrors / WriteErrorsTo / StreamFile can write error messages
	//   into this 1-based column index.
	ErrorColumnIndex int
	// contains filtered or unexported fields
}

Options control how Excel is read and mapped.

type RowError

type RowError struct {
	ExcelRowIndex int    // Physical row index in Excel (1-based)
	LogicalIndex  int    // Logical data index (1,2,3,...) after skipping header/empty rows
	ColIndex      int    // Column index (1-based)
	ColLetter     string // Column letter, e.g. "A", "B", "C"
	Field         string // Struct field name
	Column        string // Column header or configured display name
	Value         string // Raw cell value
	Err           error  // Underlying error
}

RowError represents a detailed error for a specific row/column/field.

func Read

func Read[T any](r io.Reader, opts ...Option) ([]T, []RowError, error)

Read reads an Excel file from an io.Reader (e.g. HTTP upload, memory buffer) and returns:

  • a slice of successfully mapped objects
  • a slice of RowError for all rows with issues

func ReadFile

func ReadFile[T any](path string, opts ...Option) ([]T, []RowError, error)

ReadFile reads an Excel file from a file path and returns:

  • a slice of successfully mapped objects
  • a slice of RowError for all rows with issues

func Stream

func Stream[T any](r io.Reader, opts ...Option) ([]RowError, error)

Stream streams an Excel file from an io.Reader, calling the handler supplied via OnStreamRow(...) for each non-empty data row. It returns a slice of RowError for all rows with issues. This variant does not modify the original source (no path), but you can later call WriteErrorsTo(...) if you want to produce a new file with errors.

func StreamFile

func StreamFile[T any](path string, opts ...Option) ([]RowError, error)

StreamFile streams an Excel file from a file path, calling the handler supplied via OnStreamRow(...) for each non-empty data row. It returns a slice of RowError for all rows with issues. If ErrCol(...) is set and there are errors, it will also write error messages back into the original file in the specified column.

type RowHandler

type RowHandler[T any] func(rowIdx, logicalIdx int, obj *T, rowErrs []RowError) error

RowHandler is the per-row callback used by streaming APIs. If obj == nil, row is invalid (errors in rowErrs). If rowErrs is non-empty, obj may still be non-nil if you choose to treat soft errors.

type StreamWriter

type StreamWriter[T any] struct {
	// contains filtered or unexported fields
}

StreamWriter writes rows in streaming mode based on struct tags (`excel`, `col`, `excelcol`) and reuses the same metadata/cache as the read side.

Usage:

sw, _ := excelio.NewStreamWriterFile[Product]("out.xlsx",
    excelio.Sheet("Products"),
    excelio.Header(1),
    excelio.StartRow(2),
)
defer sw.Close()

for _, p := range products {
    _ = sw.WriteRow(&p)
}

func NewStreamWriter

func NewStreamWriter[T any](w io.Writer, opts ...Option) (*StreamWriter[T], error)

NewStreamWriter creates a streaming writer that writes Excel content to an io.Writer, such as an HTTP response or bytes.Buffer.

func NewStreamWriterFile

func NewStreamWriterFile[T any](path string, opts ...Option) (*StreamWriter[T], error)

NewStreamWriterFile creates a streaming writer that writes Excel content to a file path. It uses the same Options semantics as the reader side (Sheet, Header, StartRow).

func (*StreamWriter[T]) Close

func (sw *StreamWriter[T]) Close() error

Close flushes the stream and writes/saves the workbook. It is safe to call Close multiple times; subsequent calls are no-ops.

func (*StreamWriter[T]) WriteRow

func (sw *StreamWriter[T]) WriteRow(obj *T) error

WriteRow writes a single struct value as one row into the sheet. T is expected to be a struct type (same requirement as the read side).

func (*StreamWriter[T]) WriteRows

func (sw *StreamWriter[T]) WriteRows(objs []T) error

WriteRows writes multiple struct values as subsequent rows.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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