Export CSV, XLSX, and bundle multiple files into ZIP in a style similar to “fast-csv” (Node.js) but written in Go.
Designed for streaming, each row is a []any, auto-converted to string/number, with support for headers, BOM, CRLF, and ZIP compression.
- No external libraries: only
encoding/csv,archive/zip,encoding/xml, etc. - Streaming support: write rows one by one, avoiding memory blow-up.
- Simple API: just pass
[]anyper row, like fast-csv. - ZIP support: compress from in-memory buffers or existing file paths.
- Go 1.20+ (recommended 1.21+)
go get github.com/aminofox/go-csvProject structure:
go-csv/
csv.go
xlsx.go
zip.go
util.go
examples/
main.go
README.md
go.mod
// Write to file
func WriteCSVFile(
filePath string,
headers []any,
data [][]any,
opt *CSVOptions,
toString ToStringFunc,
) error
// Write to writer (streaming with rows <-chan []any)
func WriteCSVToWriter(
w io.Writer,
headers []any,
rows <-chan []any,
opt *CSVOptions,
toString ToStringFunc,
) error
type CSVOptions struct {
Comma rune // default ','
UseCRLF bool // \r\n
BOM bool // write UTF-8 BOM
}
// Custom type-to-string converter
type ToStringFunc func(v any) (string, bool)Minimal XLSX (no external libraries), built using archive/zip + XML. Text cells use inlineStr, numbers use t="n".
// Write to file (single sheet)
func WriteXLSXFile(
filePath string,
sheetName string,
headers []any,
data [][]any,
) error
// Write to writer (streaming with rows <-chan []any)
func WriteXLSXToWriter(
w io.Writer,
sheetName string,
headers []any,
rows <-chan []any,
) error// Compress in-memory buffers into a zip
func ZipBuffers(outputZipPath string, files map[string][]byte) error
// Compress existing files on disk
func ZipPaths(outputZipPath string, paths []string, baseDir string) errorheaders := []any{"id", "name", "score", "active"}
data := [][]any{
{1, "alice", 9.5, true},
{2, "bob", 8.1, false},
{3, "seang", 10, true},
}
err := export.WriteCSVFile(
"out/users.csv",
headers,
data,
&export.CSVOptions{Comma: ',', BOM: true},
nil, // no custom toString
)
if err != nil { panic(err) }f, _ := os.Create("out/users_stream.csv")
defer f.Close()
rows := make(chan []any, 1024)
go func() {
for _, r := range data { rows <- r }
close(rows)
}()
err := export.WriteCSVToWriter(
f,
headers,
rows,
&export.CSVOptions{BOM: true},
nil,
)
if err != nil { panic(err) }err := export.WriteXLSXFile("out/users.xlsx", "Users", headers, data)
if err != nil { panic(err) }var buf bytes.Buffer
rowch := make(chan []any, 4)
go func() {
rowch <- []any{"A", "B", "C"}
rowch <- []any{1, 2, 3}
close(rowch)
}()
if err := export.WriteXLSXToWriter(&buf, "StreamSheet", nil, rowch); err != nil {
panic(err)
}
_ = os.WriteFile("out/stream.xlsx", buf.Bytes(), 0644)- From buffers:
csvBytes, _ := os.ReadFile("out/users.csv")
xlsxBytes, _ := os.ReadFile("out/users.xlsx")
files := map[string][]byte{
"csv/users.csv": csvBytes,
"xlsx/users.xlsx": xlsxBytes,
}
if err := export.ZipBuffers("out/exports.zip", files); err != nil {
panic(err)
}- From file paths:
paths := []string{"out/users.csv", "out/users_stream.csv", "out/users.xlsx"}
if err := export.ZipPaths("out/exports_paths.zip", paths, "out"); err != nil {
panic(err)
}If you want to control formatting (e.g. currency, datetime), provide a toString:
customToString := func(v any) (string, bool) {
switch t := v.(type) {
case float64:
return fmt.Sprintf("%.2f", t), true
case time.Time:
return t.In(time.FixedZone("UTC+7", 7*3600)).Format("2006-01-02 15:04:05"), true
default:
return "", false // fallback to DefaultToString
}
}
_ = export.WriteCSVFile("out/custom.csv", headers, data, &export.CSVOptions{BOM:true}, customToString)- XLSX:
- Single sheet (
sheet1.xml) for now. - Text as
inlineStr, numbers ast="n". No styles/formatting (dates, number formats).
- Single sheet (
- CSV: set
Commafor different locales; enableBOM: truefor Excel on Windows. - Streaming: use
chan []anyto handle very large datasets. - Zero dependencies: pure Go stdlib.
- Use streaming for large datasets (millions of rows).
- Increase channel buffer (
rows := make(chan []any, 4096)). - Minimize string formatting in hot paths; centralize in
toString.
See examples/main.go in the repo:
go run ./examplesMIT