English | 日本語 | Español | Français | 한국어 | 中文
fileprep — это библиотека Go для очистки, нормализации и валидации структурированных данных (CSV, TSV, LTSV, JSON, JSONL, Parquet и Excel) с использованием лёгких правил на основе struct-тегов. Поддерживает прозрачную работу с потоками gzip, bzip2, xz, zstd, zlib, snappy, s2 и lz4.
Я разработал nao1215/filesql, который позволяет выполнять SQL-запросы к файлам CSV, TSV, LTSV, Parquet и Excel. Также я создал nao1215/csv для валидации CSV-файлов.
Изучая машинное обучение, я понял: «Если расширить nao1215/csv для поддержки тех же форматов файлов, что и nao1215/filesql, можно объединить их для выполнения ETL-подобных операций». Эта идея привела к созданию fileprep: библиотеки, соединяющей предобработку/валидацию данных с SQL-запросами к файлам.
- Поддержка множества форматов: CSV, TSV, LTSV, JSON (.json), JSONL (.jsonl), Parquet, Excel (.xlsx)
- Поддержка сжатия: gzip (.gz), bzip2 (.bz2), xz (.xz), zstd (.zst), zlib (.z), snappy (.snappy), s2 (.s2), lz4 (.lz4)
- Привязка колонок по имени: поля автоматически соответствуют именам колонок в
snake_case, настраивается через тегname - Предобработка на основе struct-тегов (
prep): trim, lowercase, uppercase, значения по умолчанию - Валидация на основе struct-тегов (
validate): required, omitempty и другие - Опции процессора:
WithStrictTagParsing()для обнаружения ошибок конфигурации тегов,WithValidRowsOnly()для фильтрации вывода - Интеграция с filesql: возвращает
io.Readerдля прямого использования с filesql - Детальные отчёты об ошибках: информация о строке и колонке для каждой ошибки
go get github.com/nao1215/fileprep- Версия Go: 1.24 или выше
- Операционные системы:
- Linux
- macOS
- Windows
package main
import (
"fmt"
"strings"
"github.com/nao1215/fileprep"
)
// User представляет запись пользователя с предобработкой и валидацией
type User struct {
Name string `prep:"trim" validate:"required"`
Email string `prep:"trim,lowercase"`
Age string
}
func main() {
csvData := `name,email,age
John Doe ,JOHN@EXAMPLE.COM,30
Jane Smith,jane@example.com,25
`
processor := fileprep.NewProcessor(fileprep.FileTypeCSV)
var users []User
reader, result, err := processor.Process(strings.NewReader(csvData), &users)
if err != nil {
fmt.Printf("Ошибка: %v\n", err)
return
}
fmt.Printf("Обработано %d строк, %d валидных\n", result.RowCount, result.ValidRowCount)
for _, user := range users {
fmt.Printf("Имя: %q, Email: %q\n", user.Name, user.Email)
}
// reader можно передать напрямую в filesql
_ = reader
}Вывод:
Обработано 2 строк, 2 валидных
Имя: "John Doe", Email: "john@example.com"
Имя: "Jane Smith", Email: "jane@example.com"
Несколько важных моментов перед началом работы.
JSON/JSONL → одна колонка "data". fileparser преобразует каждый элемент JSON-массива или строку JSONL в единственную колонку "data". Struct должен содержать соответствующее поле:
type JSONRecord struct {
Data string `name:"data" prep:"trim" validate:"required"`
}Вывод всегда в компактном формате JSONL. Если тег prep нарушит структуру JSON — вернётся ErrInvalidJSONAfterPrep; если все строки окажутся пустыми — ErrEmptyJSONOutput.
Сопоставление колонок учитывает регистр. Поле UserName автоматически преобразуется в user_name. Заголовки User_Name, USERNAME, userName не совпадут. Используйте тег name для явного указания:
type Record struct {
UserName string // совпадает только с "user_name"
Email string `name:"EMAIL"` // совпадает точно с "EMAIL"
}Дублирующиеся заголовки → первая колонка приоритетна. При id,id,name привязывается только первый id.
Колонка отсутствует → пустая строка. Если колонки нет, значение будет "". Используйте validate:"required" для обнаружения.
Excel → только первый лист. Дополнительные листы .xlsx игнорируются.
Экономия памяти вывода → используйте ProcessToWriter. Process буферизует весь вывод в памяти. ProcessToWriter пропускает этот буфер и пишет напрямую в любой io.Writer. Входные записи по-прежнему загружаются в память для предобработки; устраняется только копия вывода:
f, _ := os.Create("output.csv")
defer f.Close()
result, err := processor.ProcessToWriter(input, &records, f)Этот пример демонстрирует всю мощь fileprep: комбинирование нескольких препроцессоров и валидаторов для очистки и валидации реальных «грязных» данных.
package main
import (
"fmt"
"strings"
"github.com/nao1215/fileprep"
)
// Employee представляет данные сотрудника с комплексной предобработкой и валидацией
type Employee struct {
// ID: дополнить до 6 цифр, должен быть числовым
EmployeeID string `name:"id" prep:"trim,pad_left=6:0" validate:"required,numeric,len=6"`
// Имя: очистить пробелы, обязательное буквенное с пробелами
FullName string `name:"name" prep:"trim,collapse_space" validate:"required,alphaspace"`
// Email: нормализовать в нижний регистр, проверить формат
Email string `prep:"trim,lowercase" validate:"required,email"`
// Отдел: нормализовать регистр, должен быть одним из разрешённых значений
Department string `prep:"trim,uppercase" validate:"required,oneof=ENGINEERING SALES MARKETING HR"`
// Зарплата: оставить только цифры, проверить диапазон
Salary string `prep:"trim,keep_digits" validate:"required,numeric,gte=30000,lte=500000"`
// Телефон: извлечь цифры, проверить формат E.164 после добавления кода страны
Phone string `prep:"trim,keep_digits,prefix=+1" validate:"e164"`
// Дата начала: проверить формат даты
StartDate string `name:"start_date" prep:"trim" validate:"required,datetime=2006-01-02"`
// ID руководителя: обязателен, если отдел не HR
ManagerID string `name:"manager_id" prep:"trim,pad_left=6:0" validate:"required_unless=Department HR"`
// Сайт: исправить отсутствующую схему, проверить URL
Website string `prep:"trim,lowercase,fix_scheme=https" validate:"url"`
}
func main() {
// «Грязные» реальные CSV-данные
csvData := `id,name,email,department,salary,phone,start_date,manager_id,website
42, John Doe ,JOHN.DOE@COMPANY.COM,engineering,"$75,000",555-123-4567,2023-01-15,000001,company.com/john
7,Jane Smith,jane@COMPANY.com, Sales ,"$120,000",(555) 987-6543,2022-06-01,000002,WWW.LINKEDIN.COM/in/jane
123,Bob Wilson,bob.wilson@company.com,HR,45000,555.111.2222,2024-03-20,,
99,Alice Brown,alice@company.com,Marketing,$88500,555-444-3333,2023-09-10,000003,https://alice.dev
`
processor := fileprep.NewProcessor(fileprep.FileTypeCSV)
var employees []Employee
_, result, err := processor.Process(strings.NewReader(csvData), &employees)
if err != nil {
fmt.Printf("Критическая ошибка: %v\n", err)
return
}
fmt.Printf("=== Результат обработки ===\n")
fmt.Printf("Всего строк: %d, Валидных: %d\n\n", result.RowCount, result.ValidRowCount)
for i, emp := range employees {
fmt.Printf("Сотрудник %d:\n", i+1)
fmt.Printf(" ID: %s\n", emp.EmployeeID)
fmt.Printf(" Имя: %s\n", emp.FullName)
fmt.Printf(" Email: %s\n", emp.Email)
fmt.Printf(" Отдел: %s\n", emp.Department)
fmt.Printf(" Зарплата: %s\n", emp.Salary)
fmt.Printf(" Телефон: %s\n", emp.Phone)
fmt.Printf(" Дата начала: %s\n", emp.StartDate)
fmt.Printf(" ID руков.: %s\n", emp.ManagerID)
fmt.Printf(" Сайт: %s\n\n", emp.Website)
}
}Вывод:
=== Результат обработки ===
Всего строк: 4, Валидных: 4
Сотрудник 1:
ID: 000042
Имя: John Doe
Email: john.doe@company.com
Отдел: ENGINEERING
Зарплата: 75000
Телефон: +15551234567
Дата начала: 2023-01-15
ID руков.: 000001
Сайт: https://company.com/john
Сотрудник 2:
ID: 000007
Имя: Jane Smith
Email: jane@company.com
Отдел: SALES
Зарплата: 120000
Телефон: +15559876543
Дата начала: 2022-06-01
ID руков.: 000002
Сайт: https://www.linkedin.com/in/jane
Сотрудник 3:
ID: 000123
Имя: Bob Wilson
Email: bob.wilson@company.com
Отдел: HR
Зарплата: 45000
Телефон: +15551112222
Дата начала: 2024-03-20
ID руков.: 000000
Сайт:
Сотрудник 4:
ID: 000099
Имя: Alice Brown
Email: alice@company.com
Отдел: MARKETING
Зарплата: 88500
Телефон: +15554443333
Дата начала: 2023-09-10
ID руков.: 000003
Сайт: https://alice.dev
При ошибках валидации fileprep предоставляет точную информацию об ошибках, включая номер строки, имя колонки и конкретную причину ошибки.
package main
import (
"fmt"
"strings"
"github.com/nao1215/fileprep"
)
// Order представляет заказ со строгими правилами валидации
type Order struct {
OrderID string `name:"order_id" validate:"required,uuid4"`
CustomerID string `name:"customer_id" validate:"required,numeric"`
Email string `validate:"required,email"`
Amount string `validate:"required,number,gt=0,lte=10000"`
Currency string `validate:"required,len=3,uppercase"`
Country string `validate:"required,alpha,len=2"`
OrderDate string `name:"order_date" validate:"required,datetime=2006-01-02"`
ShipDate string `name:"ship_date" validate:"datetime=2006-01-02,gtfield=OrderDate"`
IPAddress string `name:"ip_address" validate:"required,ip_addr"`
PromoCode string `name:"promo_code" validate:"alphanumeric"`
Quantity string `validate:"required,numeric,gte=1,lte=100"`
UnitPrice string `name:"unit_price" validate:"required,number,gt=0"`
TotalCheck string `name:"total_check" validate:"required,eqfield=Amount"`
}
func main() {
// CSV с множественными ошибками валидации
csvData := `order_id,customer_id,email,amount,currency,country,order_date,ship_date,ip_address,promo_code,quantity,unit_price,total_check
550e8400-e29b-41d4-a716-446655440000,12345,alice@example.com,500.00,USD,US,2024-01-15,2024-01-20,192.168.1.1,SAVE10,2,250.00,500.00
invalid-uuid,abc,not-an-email,-100,US,USA,2024/01/15,2024-01-10,999.999.999.999,PROMO-CODE-TOO-LONG!!,0,0,999
550e8400-e29b-41d4-a716-446655440001,,bob@test,50000,EURO,J1,not-a-date,,2001:db8::1,VALID20,101,-50,50000
123e4567-e89b-42d3-a456-426614174000,99999,charlie@company.com,1500.50,JPY,JP,2024-02-28,2024-02-25,10.0.0.1,VIP,5,300.10,1500.50
`
processor := fileprep.NewProcessor(fileprep.FileTypeCSV)
var orders []Order
_, result, err := processor.Process(strings.NewReader(csvData), &orders)
if err != nil {
fmt.Printf("Критическая ошибка: %v\n", err)
return
}
fmt.Printf("=== Отчёт о валидации ===\n")
fmt.Printf("Всего строк: %d\n", result.RowCount)
fmt.Printf("Валидных: %d\n", result.ValidRowCount)
fmt.Printf("Невалидных: %d\n", result.RowCount-result.ValidRowCount)
fmt.Printf("Всего ошибок: %d\n\n", len(result.ValidationErrors()))
if result.HasErrors() {
fmt.Println("=== Детали ошибок ===")
for _, e := range result.ValidationErrors() {
fmt.Printf("Строка %d, Колонка '%s': %s\n", e.Row, e.Column, e.Message)
}
}
}Вывод:
=== Отчёт о валидации ===
Всего строк: 4
Валидных: 1
Невалидных: 3
Всего ошибок: 23
=== Детали ошибок ===
Строка 2, Колонка 'order_id': value must be a valid UUID version 4
Строка 2, Колонка 'customer_id': value must be numeric
Строка 2, Колонка 'email': value must be a valid email address
Строка 2, Колонка 'amount': value must be greater than 0
Строка 2, Колонка 'currency': value must have exactly 3 characters
Строка 2, Колонка 'country': value must have exactly 2 characters
Строка 2, Колонка 'order_date': value must be a valid datetime in format: 2006-01-02
Строка 2, Колонка 'ip_address': value must be a valid IP address
Строка 2, Колонка 'promo_code': value must contain only alphanumeric characters
Строка 2, Колонка 'quantity': value must be greater than or equal to 1
Строка 2, Колонка 'unit_price': value must be greater than 0
Строка 2, Колонка 'ship_date': value must be greater than field OrderDate
Строка 2, Колонка 'total_check': value must equal field Amount
Строка 3, Колонка 'customer_id': value is required
Строка 3, Колонка 'email': value must be a valid email address
Строка 3, Колонка 'amount': value must be less than or equal to 10000
Строка 3, Колонка 'currency': value must have exactly 3 characters
Строка 3, Колонка 'country': value must contain only alphabetic characters
Строка 3, Колонка 'order_date': value must be a valid datetime in format: 2006-01-02
Строка 3, Колонка 'quantity': value must be less than or equal to 100
Строка 3, Колонка 'unit_price': value must be greater than 0
Строка 3, Колонка 'ship_date': value must be greater than field OrderDate
Строка 4, Колонка 'ship_date': value must be greater than field OrderDate
Можно комбинировать несколько тегов: prep:"trim,lowercase,default=N/A"
| Тег | Описание | Пример |
|---|---|---|
trim |
Удалить пробелы в начале/конце | prep:"trim" |
ltrim |
Удалить пробелы в начале | prep:"ltrim" |
rtrim |
Удалить пробелы в конце | prep:"rtrim" |
lowercase |
Преобразовать в нижний регистр | prep:"lowercase" |
uppercase |
Преобразовать в верхний регистр | prep:"uppercase" |
default=value |
Установить значение по умолчанию, если пусто | prep:"default=N/A" |
| Тег | Описание | Пример |
|---|---|---|
replace=old:new |
Заменить все вхождения | prep:"replace=;:," |
prefix=value |
Добавить строку в начало | prep:"prefix=ID_" |
suffix=value |
Добавить строку в конец | prep:"suffix=_END" |
truncate=N |
Ограничить N символами | prep:"truncate=100" |
strip_html |
Удалить HTML-теги | prep:"strip_html" |
strip_newline |
Удалить переносы строк (LF, CRLF, CR) | prep:"strip_newline" |
collapse_space |
Сжать множественные пробелы в один | prep:"collapse_space" |
| Тег | Описание | Пример |
|---|---|---|
remove_digits |
Удалить все цифры | prep:"remove_digits" |
remove_alpha |
Удалить все буквы | prep:"remove_alpha" |
keep_digits |
Оставить только цифры | prep:"keep_digits" |
keep_alpha |
Оставить только буквы | prep:"keep_alpha" |
trim_set=chars |
Удалить указанные символы с обоих концов | prep:"trim_set=@#$" |
| Тег | Описание | Пример |
|---|---|---|
pad_left=N:char |
Дополнить слева до N символов | prep:"pad_left=5:0" |
pad_right=N:char |
Дополнить справа до N символов | prep:"pad_right=10: " |
| Тег | Описание | Пример |
|---|---|---|
normalize_unicode |
Нормализовать Unicode в формат NFC | prep:"normalize_unicode" |
nullify=value |
Считать определённую строку пустой | prep:"nullify=NULL" |
coerce=type |
Преобразование типа (int, float, bool) | prep:"coerce=int" |
fix_scheme=scheme |
Добавить/исправить схему URL | prep:"fix_scheme=https" |
regex_replace=pattern:replacement |
Замена по регулярному выражению | prep:"regex_replace=\\d+:X" |
Можно комбинировать несколько тегов: validate:"required,email"
| Тег | Описание | Пример |
|---|---|---|
required |
Поле не должно быть пустым | validate:"required" |
omitempty |
Пропустить последующие валидаторы, если значение пустое | validate:"omitempty,email" |
boolean |
Должно быть true, false, 0 или 1 | validate:"boolean" |
| Тег | Описание | Пример |
|---|---|---|
alpha |
Только ASCII-буквы | validate:"alpha" |
alphaunicode |
Только Unicode-буквы | validate:"alphaunicode" |
alphaspace |
Буквы или пробелы | validate:"alphaspace" |
alphanumeric |
ASCII-буквы и цифры | validate:"alphanumeric" |
alphanumunicode |
Unicode-буквы или цифры | validate:"alphanumunicode" |
numeric |
Целое число | validate:"numeric" |
number |
Число (целое или десятичное) | validate:"number" |
ascii |
Только ASCII-символы | validate:"ascii" |
printascii |
Печатные ASCII-символы (0x20-0x7E) | validate:"printascii" |
multibyte |
Содержит многобайтовые символы | validate:"multibyte" |
| Тег | Описание | Пример |
|---|---|---|
eq=N |
Значение равно N | validate:"eq=100" |
ne=N |
Значение не равно N | validate:"ne=0" |
gt=N |
Значение больше N | validate:"gt=0" |
gte=N |
Значение больше или равно N | validate:"gte=1" |
lt=N |
Значение меньше N | validate:"lt=100" |
lte=N |
Значение меньше или равно N | validate:"lte=99" |
min=N |
Значение не меньше N | validate:"min=0" |
max=N |
Значение не больше N | validate:"max=100" |
len=N |
Ровно N символов | validate:"len=10" |
| Тег | Описание | Пример |
|---|---|---|
oneof=a b c |
Значение одно из разрешённых | validate:"oneof=active inactive" |
lowercase |
Значение в нижнем регистре | validate:"lowercase" |
uppercase |
Значение в верхнем регистре | validate:"uppercase" |
eq_ignore_case=value |
Равенство без учёта регистра | validate:"eq_ignore_case=yes" |
ne_ignore_case=value |
Неравенство без учёта регистра | validate:"ne_ignore_case=no" |
| Тег | Описание | Пример |
|---|---|---|
startswith=prefix |
Значение начинается с prefix | validate:"startswith=http" |
startsnotwith=prefix |
Значение не начинается с prefix | validate:"startsnotwith=_" |
endswith=suffix |
Значение заканчивается на suffix | validate:"endswith=.com" |
endsnotwith=suffix |
Значение не заканчивается на suffix | validate:"endsnotwith=.tmp" |
contains=substr |
Значение содержит подстроку | validate:"contains=@" |
containsany=chars |
Значение содержит любой из символов | validate:"containsany=abc" |
containsrune=r |
Значение содержит символ | validate:"containsrune=@" |
excludes=substr |
Значение не содержит подстроку | validate:"excludes=admin" |
excludesall=chars |
Значение не содержит ни одного из символов | validate:"excludesall=<>" |
excludesrune=r |
Значение не содержит символ | validate:"excludesrune=$" |
| Тег | Описание | Пример |
|---|---|---|
email |
Валидный email-адрес | validate:"email" |
uri |
Валидный URI | validate:"uri" |
url |
Валидный URL | validate:"url" |
http_url |
Валидный HTTP или HTTPS URL | validate:"http_url" |
https_url |
Валидный HTTPS URL | validate:"https_url" |
url_encoded |
URL-кодированная строка | validate:"url_encoded" |
datauri |
Валидный data URI | validate:"datauri" |
datetime=layout |
Валидная дата по формату Go | validate:"datetime=2006-01-02" |
uuid |
Валидный UUID (любая версия) | validate:"uuid" |
uuid3 |
Валидный UUID версии 3 | validate:"uuid3" |
uuid4 |
Валидный UUID версии 4 | validate:"uuid4" |
uuid5 |
Валидный UUID версии 5 | validate:"uuid5" |
ulid |
Валидный ULID | validate:"ulid" |
e164 |
Валидный номер E.164 | validate:"e164" |
latitude |
Валидная широта (-90 до 90) | validate:"latitude" |
longitude |
Валидная долгота (-180 до 180) | validate:"longitude" |
hexadecimal |
Валидная шестнадцатеричная строка | validate:"hexadecimal" |
hexcolor |
Валидный hex-код цвета | validate:"hexcolor" |
rgb |
Валидный RGB-цвет | validate:"rgb" |
rgba |
Валидный RGBA-цвет | validate:"rgba" |
hsl |
Валидный HSL-цвет | validate:"hsl" |
hsla |
Валидный HSLA-цвет | validate:"hsla" |
| Тег | Описание | Пример |
|---|---|---|
ip_addr |
Валидный IP-адрес (v4 или v6) | validate:"ip_addr" |
ip4_addr |
Валидный IPv4-адрес | validate:"ip4_addr" |
ip6_addr |
Валидный IPv6-адрес | validate:"ip6_addr" |
cidr |
Валидная CIDR-нотация | validate:"cidr" |
cidrv4 |
Валидный IPv4 CIDR | validate:"cidrv4" |
cidrv6 |
Валидный IPv6 CIDR | validate:"cidrv6" |
mac |
Валидный MAC-адрес | validate:"mac" |
fqdn |
Валидное полное доменное имя | validate:"fqdn" |
hostname |
Валидное имя хоста (RFC 952) | validate:"hostname" |
hostname_rfc1123 |
Валидное имя хоста (RFC 1123) | validate:"hostname_rfc1123" |
hostname_port |
Валидный hostname:port | validate:"hostname_port" |
| Тег | Описание | Пример |
|---|---|---|
eqfield=Field |
Значение равно другому полю | validate:"eqfield=Password" |
nefield=Field |
Значение не равно другому полю | validate:"nefield=OldPassword" |
gtfield=Field |
Значение больше другого поля | validate:"gtfield=MinPrice" |
gtefield=Field |
Значение >= другого поля | validate:"gtefield=StartDate" |
ltfield=Field |
Значение меньше другого поля | validate:"ltfield=MaxPrice" |
ltefield=Field |
Значение <= другого поля | validate:"ltefield=EndDate" |
fieldcontains=Field |
Значение содержит значение другого поля | validate:"fieldcontains=Keyword" |
fieldexcludes=Field |
Значение исключает значение другого поля | validate:"fieldexcludes=Forbidden" |
| Тег | Описание | Пример |
|---|---|---|
required_if=Field value |
Обязательно, если поле равно значению | validate:"required_if=Status active" |
required_unless=Field value |
Обязательно, если поле не равно значению | validate:"required_unless=Type guest" |
required_with=Field |
Обязательно, если поле присутствует | validate:"required_with=Email" |
required_without=Field |
Обязательно, если поле отсутствует | validate:"required_without=Phone" |
Примеры:
type User struct {
Role string
// Profile обязателен, когда Role равен "admin", необязателен для других ролей
Profile string `validate:"required_if=Role admin"`
// Bio обязателен, если Role не равен "guest"
Bio string `validate:"required_unless=Role guest"`
}
type Contact struct {
Email string
Phone string
// Name обязателен, когда Email не пуст
Name string `validate:"required_with=Email"`
// Хотя бы один из Email или BackupEmail должен быть указан
BackupEmail string `validate:"required_without=Email"`
}| Формат | Расширение | Сжатые форматы |
|---|---|---|
| CSV | .csv |
.csv.gz, .csv.bz2, .csv.xz, .csv.zst, .csv.z, .csv.snappy, .csv.s2, .csv.lz4 |
| TSV | .tsv |
.tsv.gz, .tsv.bz2, .tsv.xz, .tsv.zst, .tsv.z, .tsv.snappy, .tsv.s2, .tsv.lz4 |
| LTSV | .ltsv |
.ltsv.gz, .ltsv.bz2, .ltsv.xz, .ltsv.zst, .ltsv.z, .ltsv.snappy, .ltsv.s2, .ltsv.lz4 |
| JSON | .json |
.json.gz, .json.bz2, .json.xz, .json.zst, .json.z, .json.snappy, .json.s2, .json.lz4 |
| JSONL | .jsonl |
.jsonl.gz, .jsonl.bz2, .jsonl.xz, .jsonl.zst, .jsonl.z, .jsonl.snappy, .jsonl.s2, .jsonl.lz4 |
| Excel | .xlsx |
.xlsx.gz, .xlsx.bz2, .xlsx.xz, .xlsx.zst, .xlsx.z, .xlsx.snappy, .xlsx.s2, .xlsx.lz4 |
| Parquet | .parquet |
.parquet.gz, .parquet.bz2, .parquet.xz, .parquet.zst, .parquet.z, .parquet.snappy, .parquet.s2, .parquet.lz4 |
| Формат | Расширение | Описание |
|---|---|---|
| gzip | .gz |
GNU zip сжатие, широко используется |
| bzip2 | .bz2 |
Блочное сжатие с отличной степенью |
| xz | .xz |
Высокопроизводительное LZMA2 сжатие |
| zstd | .zst |
Zstandard от Facebook |
| zlib | .z |
Стандартное DEFLATE сжатие |
| snappy | .snappy |
Быстрое сжатие от Google |
| s2 | .s2 |
Улучшенное расширение Snappy |
| lz4 | .lz4 |
Сверхбыстрое сжатие |
Примечание о сжатии Parquet: Внешнее сжатие (.parquet.gz и т.д.) применяется к самому файлу-контейнеру. Файлы Parquet также могут использовать внутреннее сжатие (Snappy, GZIP, LZ4, ZSTD), которое прозрачно обрабатывается библиотекой parquet-go.
// Обработка файла с предобработкой и валидацией
processor := fileprep.NewProcessor(fileprep.FileTypeCSV)
var records []MyRecord
reader, result, err := processor.Process(file, &records)
if err != nil {
return err
}
// Проверка ошибок валидации
if result.HasErrors() {
for _, e := range result.ValidationErrors() {
log.Printf("Строка %d, Колонка %s: %s", e.Row, e.Column, e.Message)
}
}
// Передача предобработанных данных в filesql с использованием Builder
ctx := context.Background()
builder := filesql.NewBuilder().
AddReader(reader, "my_table", filesql.FileTypeCSV)
validatedBuilder, err := builder.Build(ctx)
if err != nil {
return err
}
db, err := validatedBuilder.Open(ctx)
if err != nil {
return err
}
defer db.Close()
// Выполнение SQL-запросов к предобработанным данным
rows, err := db.QueryContext(ctx, "SELECT * FROM my_table WHERE age > 20")NewProcessor принимает функциональные опции для настройки поведения:
По умолчанию недопустимые аргументы тегов (например, eq=abc, где ожидается число) молча игнорируются. Включите строгий режим для обнаружения таких ошибок конфигурации:
processor := fileprep.NewProcessor(fileprep.FileTypeCSV, fileprep.WithStrictTagParsing())
var records []MyRecord
// Возвращает ошибку, если аргумент тега недопустим (например, "eq=abc", "truncate=xyz")
_, _, err := processor.Process(input, &records)По умолчанию вывод включает все строки (как валидные, так и невалидные). Используйте WithValidRowsOnly для фильтрации вывода только валидными строками:
processor := fileprep.NewProcessor(fileprep.FileTypeCSV, fileprep.WithValidRowsOnly())
var records []MyRecord
reader, result, err := processor.Process(input, &records)
// reader содержит только строки, прошедшие все валидации
// records содержит только валидные структуры
// result.RowCount включает все строки; result.ValidRowCount содержит количество валидных
// result.Errors по-прежнему сообщает обо всех ошибках валидацииОпции можно комбинировать:
processor := fileprep.NewProcessor(fileprep.FileTypeCSV,
fileprep.WithStrictTagParsing(),
fileprep.WithValidRowsOnly(),
)Поля структуры сопоставляются с колонками файла по имени, а не по позиции. Имена полей автоматически преобразуются в snake_case для соответствия заголовкам колонок. Порядок колонок не имеет значения.
type User struct {
UserName string `name:"user"` // соответствует колонке "user" (не "user_name")
Email string `name:"mail_addr"` // соответствует колонке "mail_addr" (не "email")
Age string // соответствует колонке "age" (авто snake_case)
}Если ваши ключи LTSV используют дефисы (user-id) или колонки Parquet/XLSX используют camelCase (userId), используйте тег name для указания точного имени колонки.
Смотрите Что нужно знать для правил чувствительности к регистру, поведения дублирующихся заголовков и обработки отсутствующих колонок.
fileprep загружает весь файл в память для обработки. Это позволяет произвольный доступ и многопроходные операции, но имеет последствия для больших файлов:
| Размер файла | Примерная память | Рекомендация |
|---|---|---|
| < 100 МБ | ~2-3x размера файла | Прямая обработка |
| 100-500 МБ | ~500 МБ - 1.5 ГБ | Мониторинг памяти, рассмотреть разбиение |
| > 500 МБ | > 1.5 ГБ | Разделить файлы или использовать потоковые альтернативы |
Для сжатых входных данных (gzip, bzip2, xz, zstd, zlib, snappy, s2, lz4) использование памяти основано на распакованном размере.
Результаты бенчмарков обработки CSV-файлов со сложной структурой, содержащей 21 колонку. Каждое поле использует несколько тегов предобработки и валидации:
Используемые теги предобработки: trim, lowercase, uppercase, keep_digits, pad_left, strip_html, strip_newline, collapse_space, truncate, fix_scheme, default
Используемые теги валидации: required, alpha, numeric, email, uuid, ip_addr, url, oneof, min, max, len, printascii, ascii, eqfield
| Записей | Время | Память | Аллокаций |
|---|---|---|---|
| 100 | 0.6 мс | 0.9 МБ | 7,654 |
| 1,000 | 6.1 мс | 9.6 МБ | 74,829 |
| 10,000 | 69 мс | 101 МБ | 746,266 |
| 50,000 | 344 мс | 498 МБ | 3,690,281 |
# Быстрый бенчмарк (100 и 1,000 записей)
make bench
# Полный бенчмарк (все размеры включая 50,000 записей)
make bench-allТестировано на AMD Ryzen AI MAX+ 395, Go 1.24, Linux. Результаты зависят от оборудования.
- nao1215/filesql - SQL-драйвер для CSV, TSV, LTSV, Parquet, Excel с поддержкой gzip, bzip2, xz, zstd.
- nao1215/fileframe - DataFrame API для CSV/TSV/LTSV, Parquet, Excel.
- nao1215/csv - Чтение CSV с валидацией и простой DataFrame на Go.
- go-playground/validator - Валидация структур и полей Go, включая кросс-полевую, кросс-структурную, Map, Slice и Array валидацию
- shogo82148/go-header-csv - go-header-csv — кодировщик/декодировщик CSV с заголовком.
Мы приветствуем вклад в проект! Подробнее см. Contributing Guide.
Если вы находите этот проект полезным, пожалуйста, рассмотрите:
- Поставить звезду на GitHub — это помогает другим найти проект
- Стать спонсором — ваша поддержка помогает поддерживать проект и мотивирует на дальнейшую разработку
Ваша поддержка, будь то звёзды, спонсорство или вклад в код, движет этот проект вперёд. Спасибо!
Этот проект лицензирован под лицензией MIT — подробности см. в файле LICENSE.
