forked from gocarina/gocsv
-
Notifications
You must be signed in to change notification settings - Fork 0
/
unmarshaller.go
145 lines (126 loc) · 3.99 KB
/
unmarshaller.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package commando
import (
"context"
"errors"
"fmt"
"io"
"reflect"
)
// Unmarshaller is a CSV to struct unmarshaller.
type Unmarshaller struct {
config *validConfig
line int
reader Reader
}
// NewUnmarshaller is a convenience function which allocates and
// returns a new Unmarshaller.
func NewUnmarshaller(holder interface{}, reader Reader) (*Unmarshaller, error) {
return (&Config{Holder: holder}).NewUnmarshaller(reader)
}
// NewUnmarshaller creates an unmarshaller from a Reader and a struct.
func (c *Config) NewUnmarshaller(reader Reader) (*Unmarshaller, error) {
headers, err := reader.Read()
if err != nil {
return nil, err
}
vc, err := c.validate(headers)
if err != nil {
return nil, err
}
um := &Unmarshaller{
reader: reader,
config: vc,
line: 1,
}
return um, nil
}
// Read returns an interface{} whose runtime type is the same as the
// struct that was used to create the Unmarshaller.
func (um *Unmarshaller) Read() (interface{}, error) {
value, _, err := um.ReadUnmatched()
return value, err
}
// ReadAll returns a slice of structs.
func (um *Unmarshaller) ReadAll(ctx context.Context, onError func(ctx context.Context, err error) error) (interface{}, error) {
out := reflect.MakeSlice(reflect.SliceOf(um.config.outType), 0, 0)
err := um.ReadAllCallback(ctx, func(_ context.Context, rec interface{}) error {
out = reflect.Append(out, reflect.ValueOf(rec))
return nil
}, onError)
return out.Interface(), err
}
// ReadAllCallback calls onSuccess for every record Read() from um.
//
// If Read() returns an error, it's passed to onError(), which decides
// whether to continue processing or stop. If onError() returns nil,
// processing continues; if it returns an error, processing stops and
// its error (not the one returned by Read()) is returned.
//
// If onSuccess() returns an error, processing stops and its error is
// returned.
func (um *Unmarshaller) ReadAllCallback(ctx context.Context,
onSuccess func(context.Context, interface{}) error,
onError func(context.Context, error) error) error {
for {
rec, err := um.Read()
if errors.Is(err, io.EOF) {
return nil
} else if err != nil {
if handlerErr := onError(ctx, err); handlerErr != nil {
return handlerErr
}
continue
}
if err := onSuccess(ctx, rec); err != nil {
return err
}
}
return nil
}
// wrapLine wraps err, including the line the error occurred on.
func wrapLine(err error, line int) error {
if err != nil {
return fmt.Errorf("on line %d: %w", line, err)
}
return err
}
// ReadUnmatched is same as Read(), but returns a map of the columns
// that didn't match a field in the struct.
func (um *Unmarshaller) ReadUnmatched() (interface{}, map[string]string, error) {
row, err := um.reader.Read()
if err != nil {
return nil, nil, err
}
out, unmatched, err := um.unmarshalRow(row)
if err != nil {
um.line++
}
return out, unmatched, wrapLine(err, um.line)
}
// createNew allocates and returns a new holder to unmarshal data
// into.
func (um *Unmarshaller) createNew() (reflect.Value, bool) {
isPointer := false
concreteOutType := um.config.outType
if um.config.outType.Kind() == reflect.Ptr {
isPointer = true
concreteOutType = concreteOutType.Elem()
}
outValue := createNewOutInner(isPointer, concreteOutType)
return outValue, isPointer
}
// unmarshalRow converts a CSV row to a struct, based on CSV struct
// tags.
func (um *Unmarshaller) unmarshalRow(row []string) (interface{}, map[string]string, error) {
unmatched := make(map[string]string)
outValue, isPointer := um.createNew()
for j, csvColumnContent := range row {
if j < len(um.config.fieldInfoMap) && um.config.fieldInfoMap[j] != nil {
fieldInfo := um.config.fieldInfoMap[j]
if err := setInnerField(&outValue, isPointer, fieldInfo.IndexChain, csvColumnContent, fieldInfo.omitEmpty); err != nil { // Set field of struct
return nil, nil, fmt.Errorf("cannot assign field at %v to %T through index chain %v: %v", j, outValue, fieldInfo.IndexChain, err)
}
}
}
return outValue.Interface(), unmatched, nil
}