English | 日本語 | Español | Français | Русский | 中文
fileprep은 CSV, TSV, LTSV, JSON, JSONL, Parquet, Excel 등의 구조화된 데이터를 경량 struct 태그 규칙으로 정리, 정규화, 검증하기 위한 Go 라이브러리입니다. gzip, bzip2, xz, zstd, zlib, snappy, s2, lz4 스트림을 원활하게 지원합니다.
저는 CSV, TSV, LTSV, Parquet, Excel 파일에 SQL 쿼리를 실행할 수 있는 nao1215/filesql을 개발했습니다. 또한 CSV 파일 검증을 위해 nao1215/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과의 원활한 통합: filesql에서 직접 사용할 수 있는
io.Reader반환 - 상세한 에러 보고: 각 에러의 행과 열 정보
go get github.com/nao1215/fileprep- Go 버전: 1.24 이상
- 지원 OS:
- 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("Error: %v\n", err)
return
}
fmt.Printf("처리 완료: %d행 중 %d행이 유효\n", result.RowCount, result.ValidRowCount)
for _, user := range users {
fmt.Printf("Name: %q, Email: %q\n", user.Name, user.Email)
}
// reader는 filesql에 직접 전달할 수 있습니다
_ = reader
}출력:
처리 완료: 2행 중 2행이 유효
Name: "John Doe", Email: "john@example.com"
Name: "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 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"`
// 시작일: datetime 형식 검증
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(" 이메일: %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
이메일: john.doe@company.com
부서: ENGINEERING
급여: 75000
전화번호: +15551234567
시작일: 2023-01-15
관리자 ID: 000001
웹사이트: https://company.com/john
직원 2:
ID: 000007
이름: Jane Smith
이메일: jane@company.com
부서: SALES
급여: 120000
전화번호: +15559876543
시작일: 2022-06-01
관리자 ID: 000002
웹사이트: https://www.linkedin.com/in/jane
직원 3:
ID: 000123
이름: Bob Wilson
이메일: bob.wilson@company.com
부서: HR
급여: 45000
전화번호: +15551112222
시작일: 2024-03-20
관리자 ID: 000000
웹사이트:
직원 4:
ID: 000099
이름: Alice Brown
이메일: 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 |
접두사로 시작 | validate:"startswith=http" |
startsnotwith=prefix |
접두사로 시작하지 않음 | validate:"startsnotwith=_" |
endswith=suffix |
접미사로 끝남 | validate:"endswith=.com" |
endsnotwith=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 |
유효한 이메일 주소 | 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 |
유효한 데이터 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 |
유효한 16진수 문자열 | validate:"hexadecimal" |
hexcolor |
유효한 16진수 색상 코드 | 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 |
유효한 호스트명:포트 | 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 |
필드가 value와 같으면 필수 | validate:"required_if=Status active" |
required_unless=Field value |
필드가 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
// Role이 "admin"일 때 Profile은 필수, 다른 역할에서는 선택
Profile string `validate:"required_if=Role admin"`
// Role이 "guest"가 아닌 한 Bio는 필수
Bio string `validate:"required_unless=Role guest"`
}
type Contact struct {
Email string
Phone string
// Email이 비어있지 않을 때 Name은 필수
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 |
Facebook의 Zstandard 압축 |
| 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)
}
}
// Builder 패턴을 사용하여 전처리된 데이터를 filesql에 전달
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에는 유효한 struct만 포함됩니다
// result.RowCount는 모든 행을 포함하고, result.ValidRowCount는 유효한 행 수입니다
// result.Errors는 모든 검증 실패를 보고합니다옵션은 조합할 수 있습니다:
processor := fileprep.NewProcessor(fileprep.FileTypeCSV,
fileprep.WithStrictTagParsing(),
fileprep.WithValidRowsOnly(),
)struct 필드는 이름으로 파일 컬럼에 매핑되며, 위치가 아닙니다. 필드 이름은 자동으로 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 MB | 파일 크기의 약 2-3배 | 직접 처리 |
| 100-500 MB | 500 MB - 1.5 GB | 메모리 모니터링, 청크 처리 고려 |
| > 500 MB | > 1.5 GB | 파일 분할 또는 스트리밍 대안 사용 |
압축 입력(gzip, bzip2, xz, zstd, zlib, snappy, s2, lz4)의 경우 메모리 사용량은 압축 해제 후 크기를 기준으로 합니다.
21개 컬럼을 가진 복잡한 struct로 CSV 파일을 처리하는 벤치마크 결과입니다. 각 필드는 여러 전처리 및 검증 태그를 사용합니다:
사용된 전처리 태그: 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
| 레코드 수 | 시간 | 메모리 | Allocs/op |
|---|---|---|---|
| 100 | 0.6 ms | 0.9 MB | 7,654 |
| 1,000 | 6.1 ms | 9.6 MB | 74,829 |
| 10,000 | 69 ms | 101 MB | 746,266 |
| 50,000 | 344 ms | 498 MB | 3,690,281 |
# 빠른 벤치마크 (100 및 1,000 레코드)
make bench
# 전체 벤치마크 (50,000 레코드를 포함한 모든 크기)
make bench-allAMD Ryzen AI MAX+ 395, Go 1.24, Linux에서 테스트. 결과는 하드웨어에 따라 다릅니다.
- nao1215/filesql - CSV, TSV, LTSV, Parquet, Excel용 SQL 드라이버. gzip, bzip2, xz, zstd 지원.
- nao1215/fileframe - CSV/TSV/LTSV, Parquet, Excel을 위한 DataFrame API.
- nao1215/csv - 검증 기능이 있는 CSV 읽기 및 간단한 DataFrame in golang.
- go-playground/validator - Go Struct 및 Field 검증, Cross Field, Cross Struct, Map, Slice, Array diving 포함.
- shogo82148/go-header-csv - 헤더가 있는 csv의 인코더/디코더.
기여를 환영합니다! 자세한 내용은 Contributing Guide를 참조하세요.
이 프로젝트가 유용하다면 다음을 고려해 주세요:
- GitHub에서 스타 주기 - 다른 사람들이 프로젝트를 발견하는 데 도움이 됩니다
- 스폰서 되기 - 여러분의 지원이 프로젝트를 유지하고 지속적인 개발의 동기가 됩니다
스타, 스폰서십 또는 기여를 통한 여러분의 지원이 이 프로젝트를 앞으로 나아가게 합니다. 감사합니다!
이 프로젝트는 MIT 라이선스 하에 배포됩니다 - 자세한 내용은 LICENSE 파일을 참조하세요.
