跳到主要内容

CSV

csv 包使用与 Excel 包相同的 tabular 标签系统提供 CSV 导入/导出功能。它共享相同的 tabular.Importertabular.Exporter 接口,便于在格式之间切换。

概述

操作入口说明
导入csv.NewImporterFor[T]()将 CSV 文件/读取器解析为类型化结构体切片
导出csv.NewExporterFor[T]()将结构体切片写入 CSV 文件/缓冲区

模型定义

CSV 使用与 Excel 相同的 tabular 结构体标签。完整标签参考请查看 Excel 文档

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

Name string `tabular:"姓名,width:20"`
Email string `tabular:"邮箱,width:30"`
Department string `tabular:"部门"`
JoinDate timex.Date `tabular:"入职日期,format:2006-01-02"`
Salary decimal.Decimal `tabular:"薪资"`
IsActive bool `tabular:"是否在职"`
}

导出

基本导出

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

exporter := csv.NewExporterFor[Employee]()

// 导出到文件
err := exporter.ExportToFile(employees, "employees.csv")

// 导出到缓冲区(用于 HTTP 响应)
buf, err := exporter.Export(employees)

导出选项

选项默认值说明
csv.WithExportDelimiter(rune),字段分隔符
csv.WithoutWriteHeader()写入表头跳过表头行
csv.WithCRLF()仅 LF使用 Windows 风格 CRLF 换行符
// TSV 导出,使用 Windows 换行符
exporter := csv.NewExporterFor[Employee](
csv.WithExportDelimiter('\t'),
csv.WithCRLF(),
)

// 不写入表头行
exporter := csv.NewExporterFor[Employee](
csv.WithoutWriteHeader(),
)

自定义格式化器

exporter := csv.NewExporterFor[Employee]()

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

导入

基本导入

importer := csv.NewImporterFor[Employee]()

// 从文件导入
data, importErrors, err := importer.ImportFromFile("employees.csv")
if err != nil {
return err // 致命错误
}

// 检查行级错误
for _, e := range importErrors {
log.Printf("第 %d 行:%v", e.Row, e.Err)
}

employees := data.([]Employee)

从 io.Reader 导入

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

导入选项

选项默认值说明
csv.WithImportDelimiter(rune),字段分隔符
csv.WithoutHeader()有表头CSV 无表头行;按位置映射列
csv.WithSkipRows(n)0跳过表头前的前导行
csv.WithoutTrimSpace()启用修剪禁用自动空白修剪
csv.WithComment(rune)注释字符(以此开头的行被跳过)
// TSV 文件,有 2 行标题,# 开头为注释行
importer := csv.NewImporterFor[Employee](
csv.WithImportDelimiter('\t'),
csv.WithSkipRows(2),
csv.WithComment('#'),
)

// 无表头的 CSV(按位置/顺序匹配列)
importer := csv.NewImporterFor[Employee](
csv.WithoutHeader(),
)

有表头 vs 无表头模式

模式列映射方式
有表头(默认)表头名称 → tabular 标签名
无表头列位置 → tabular 字段顺序

使用 WithoutHeader() 时,列按位置匹配。使用 order 标签属性控制字段排序:

type Record struct {
Name string `tabular:"姓名,order:0"`
Email string `tabular:"邮箱,order:1"`
Age int `tabular:"年龄,order:2"`
}

自定义解析器

importer := csv.NewImporterFor[Employee]()

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

验证

导入的记录会自动使用 validator.Validate(...) 进行验证,与 Excel 导入器相同。

错误处理

错误含义
ErrDataMustBeSlice导出数据必须是切片
ErrNoDataRowsFound文件中没有数据行
ErrDuplicateColumnName表头中存在重复列名
ErrFieldNotSettable结构体字段无法设置

CSV vs Excel

特性CSVExcel
文件格式.csv(纯文本).xlsx(二进制)
多工作表不支持支持
列宽度忽略应用
分隔符可配置不适用
注释行支持不适用
空白修剪可配置不适用
换行符LF 或 CRLF不适用
依赖Go 标准库excelize

两个包都实现了 tabular.Importer / tabular.Exporter 接口,因此可以在不更改模型定义的情况下互换使用。