-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparser.go
130 lines (110 loc) · 2.66 KB
/
parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package csv2structs
import (
"bytes"
"encoding/csv"
"fmt"
"io"
"reflect"
"strconv"
)
// Parser can be used to read a single or all structs from an io.Reader containing CSV data
type Parser[T any] interface {
Read() (*T, error)
ReadAll() ([]*T, error)
}
type parser[T any] struct {
opts *options
reader *csv.Reader
fields []reflect.StructField
header map[string]int
}
// NewParser returns a new Parser implementation for the given io.Reader containing CSV data
func NewParser[T any](reader io.Reader, opts ...Option) (Parser[T], error) {
fields, err := getFields[T]()
if err != nil {
return nil, err
}
bom := make([]byte, 3)
_, err = reader.Read(bom)
if err != nil {
return nil, err
}
if !bytes.Equal(bom, []byte{0xEF, 0xBB, 0xBF}) {
reader = io.MultiReader(bytes.NewReader(bom), reader)
}
r := csv.NewReader(reader)
header, err := r.Read()
if err != nil {
return nil, err
}
o := getOptions(opts)
h := getHeader(o, header)
headerMap, err := mapHeader(fields, h)
if err != nil {
return nil, err
}
p := &parser[T]{
opts: o,
reader: r,
fields: fields,
header: headerMap,
}
return p, nil
}
// Read a single struct from the CSV data
func (p *parser[T]) Read() (*T, error) {
row, err := p.reader.Read()
if err != nil {
return nil, err
}
var obj T
for f, i := range p.header {
field := reflect.ValueOf(&obj).Elem().FieldByName(f)
switch field.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val, err := strconv.ParseInt(row[i], 10, 64)
if err != nil {
return nil, fmt.Errorf(`failed to convert "%s" to int`, row[i])
}
field.SetInt(val)
case reflect.String:
field.SetString(row[i])
case reflect.Bool:
val, err := strconv.ParseBool(row[i])
if err != nil {
return nil, fmt.Errorf(`failed to convert "%s" to bool`, row[i])
}
field.SetBool(val)
case reflect.Float64, reflect.Float32:
val, err := strconv.ParseFloat(row[i], 64)
if err != nil {
return nil, fmt.Errorf(`failed to convert "%s" to float`, row[i])
}
field.SetFloat(val)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val, err := strconv.ParseUint(row[i], 10, 64)
if err != nil {
return nil, fmt.Errorf(`failed to convert "%s" to uint`, row[i])
}
field.SetUint(val)
default:
return nil, fmt.Errorf("unsupported type: %s", field.Kind())
}
}
return &obj, nil
}
// ReadAll reads all structs from the CSV data
func (p *parser[T]) ReadAll() ([]*T, error) {
var objects []*T
for {
obj, err := p.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
objects = append(objects, obj)
}
return objects, nil
}