-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcsv.go
276 lines (251 loc) · 8.36 KB
/
csv.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
package csv
import (
"encoding/csv"
"errors"
"os"
"reflect"
"slices"
)
// 默认csv设置
var DefaultOption = CsvOption{
ColumnNameRowIndex: 0,
DataBeginRowIndex: 1, // csv行索引
SliceSeparator: ";",
KvSeparator: "_",
PairSeparator: "#",
}
// 字段转换接口
type FieldConverter func(obj any, columnName, fieldStr string) any
type CsvOption struct {
// 数据行索引(>=1)
DataBeginRowIndex int
// 字段名数据行索引(>=0)
ColumnNameRowIndex int
// key-value格式的csv数据给对象赋值,数据行索引(>=0)
ObjectDataBeginRowIndex int
// 是否禁用protobuf的字段别名(struct tag里的name),默认不禁用
// proto2示例 Num *int32 `protobuf:"varint,1,opt,name=num"`
// proto3示例 Num int32 `protobuf:"varint,1,opt,name=num,proto3"`
DisableProtobufAliasName bool
// 是否禁用json的字段别名(struct tag里的name),默认不禁用
// 示例 Num *int32 `json:"num,omitempty"`
DisableJsonAliasName bool
// 数组分隔符
// 如数组分隔符为;时,则1;2;3可以表示[1,2,3]的数组
SliceSeparator string
// Key-Value分隔符
// 如KvSeparator为_ PairSeparator为#
// 则a_1#b_2#c_3可以表示{"a":1,"b":2,"c":3}的map或者如下结构体
// type S struct {
// a string
// b string
// c int
// }
KvSeparator string
// 不同Key-Value之间的分隔符
// 如KvSeparator为_ PairSeparator为#
// 则a_1#b_2#c_3可以表示{"a":1,"b":2,"c":3}的map或者如下结构体
// type S struct {
// a string
// b string
// c int
// }
PairSeparator string
// 自定义转换函数
// 把csv的字符串转换成其他对象 以列名作为关键字
customFieldConvertersByColumnName map[string]FieldConverter
// 把csv的字符串转换成其他对象 以字段类型作为关键字
customFieldConvertersByType map[reflect.Type]FieldConverter
}
// 注册列名对应的转换接口
func (co *CsvOption) RegisterConverterByColumnName(columnName string, converter FieldConverter) *CsvOption {
if co.customFieldConvertersByColumnName == nil {
co.customFieldConvertersByColumnName = make(map[string]FieldConverter)
}
co.customFieldConvertersByColumnName[columnName] = converter
return co
}
func (co *CsvOption) GetConverterByColumnName(columnName string) FieldConverter {
if co.customFieldConvertersByColumnName == nil {
return nil
}
return co.customFieldConvertersByColumnName[columnName]
}
// 注册类型对应的转换接口
func (co *CsvOption) RegisterConverterByType(typ reflect.Type, converter FieldConverter) *CsvOption {
if co.customFieldConvertersByType == nil {
co.customFieldConvertersByType = make(map[reflect.Type]FieldConverter)
}
co.customFieldConvertersByType[typ] = converter
return co
}
func (co *CsvOption) GetConverterByType(typ reflect.Type) FieldConverter {
if co.customFieldConvertersByType == nil {
return nil
}
return co.customFieldConvertersByType[typ]
}
// 如果typ是Struct,但是注册的FieldConverter是同类型的Ptr,则会返回Ptr类型的FieldConverter,同时convertToElem返回true
func (co *CsvOption) GetConverterByTypePtrOrStruct(typ reflect.Type) (converter FieldConverter, convertToElem bool) {
if co.customFieldConvertersByType == nil {
return
}
converter, _ = co.customFieldConvertersByType[typ]
if converter == nil {
if typ.Kind() == reflect.Struct {
converter = co.GetConverterByType(reflect.PointerTo(typ))
// 注册的是指针类型,转换后,需要把ptr转换成elem
convertToElem = converter != nil
return
}
}
return
}
type IntOrString interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~string
}
// csv数据转换成map
// V支持proto.Message和普通struct结构
func ReadCsvFileMap[M ~map[K]V, K IntOrString, V any](file string, m M, option *CsvOption) error {
rows, readErr := ReadCsvFile(file)
if readErr != nil {
return readErr
}
return ReadCsvFromDataMap(rows, m, option)
}
// csv数据转换成slice
// V支持proto.Message和普通struct结构
func ReadCsvFileSlice[Slice ~[]V, V any](file string, s Slice, option *CsvOption) (Slice, error) {
rows, readErr := ReadCsvFile(file)
if readErr != nil {
return s, readErr
}
return ReadCsvFromDataSlice(rows, s, option)
}
// key-value格式的csv数据给对象赋值
// V支持proto.Message和普通struct结构
func ReadCsvFileObject[V any](file string, v V, option *CsvOption) error {
rows, readErr := ReadCsvFile(file)
if readErr != nil {
return readErr
}
return ReadCsvFromDataObject(rows, v, option)
}
func ReadCsvFile(file string) ([][]string, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
return csv.NewReader(f).ReadAll()
}
// csv数据转换成map
// V支持proto.Message和普通struct结构
func ReadCsvFromDataMap[M ~map[K]V, K IntOrString, V any](rows [][]string, m M, option *CsvOption) error {
if option == nil {
option = &DefaultOption
}
if len(rows) == 0 {
return errors.New("no csv header")
}
if len(rows) <= option.ColumnNameRowIndex {
return errors.New("no column name header")
}
columnNames := rows[option.ColumnNameRowIndex]
if len(columnNames) == 0 {
return errors.New("no column")
}
if option.DataBeginRowIndex < 1 {
return errors.New("DataBeginRowIndex must >=1")
}
mType := reflect.TypeOf(m)
mVal := reflect.ValueOf(m)
keyType := mType.Key() // key type of m, 如int
valueType := mType.Elem() // value type of m, 如*pb.ItemCfg or pb.ItemCfg
for rowIndex := option.DataBeginRowIndex; rowIndex < len(rows); rowIndex++ {
row := rows[rowIndex]
// 固定第一列是key
key := ConvertStringToRealType(keyType, row[0])
value := ConvertCsvLineToValue(valueType, row, columnNames, option)
mVal.SetMapIndex(reflect.ValueOf(key), value)
}
return nil
}
// csv数据转换成slice
// V支持proto.Message和普通struct结构
func ReadCsvFromDataSlice[Slice ~[]V, V any](rows [][]string, s Slice, option *CsvOption) (Slice, error) {
if option == nil {
option = &DefaultOption
}
if len(rows) == 0 {
return s, errors.New("no csv header")
}
if len(rows) <= option.ColumnNameRowIndex {
return s, errors.New("no column name header")
}
columnNames := rows[option.ColumnNameRowIndex]
if len(columnNames) == 0 {
return s, errors.New("no column")
}
if option.DataBeginRowIndex < 1 {
return s, errors.New("DataBeginRowIndex must >=1")
}
sType := reflect.TypeOf(s)
valueType := sType.Elem() // value type of s, 如*pb.ItemCfg or pb.ItemCfg
for rowIndex := option.DataBeginRowIndex; rowIndex < len(rows); rowIndex++ {
row := rows[rowIndex]
value := ConvertCsvLineToValue(valueType, row, columnNames, option)
s = slices.Insert(s, len(s), value.Interface().(V)) // s = append(s, value)
}
return s, nil
}
// key-value格式的csv数据转换成对象
// V支持proto.Message和普通struct结构
func ReadCsvFromDataObject[V any](rows [][]string, v V, option *CsvOption) error {
if option == nil {
option = &DefaultOption
}
if len(rows) == 0 {
return errors.New("no csv header")
}
if len(rows[0]) < 2 {
return errors.New("column count must >= 2")
}
if option.ObjectDataBeginRowIndex < 1 {
return errors.New("ObjectDataBeginRowIndex must >=1")
}
typ := reflect.TypeOf(v) // type of v, 如*pb.ItemCfg or pb.ItemCfg
val := reflect.ValueOf(v)
if typ.Kind() != reflect.Ptr {
return errors.New("v must be Ptr")
}
valElem := val.Elem() // *pb.ItemCfg -> pb.ItemCfg
// protobuf alias name map
var aliasNames map[string]string
for rowIndex := option.ObjectDataBeginRowIndex; rowIndex < len(rows); rowIndex++ {
row := rows[rowIndex]
// key-value的固定格式,列名不用
columnName := row[0]
fieldString := row[1]
fieldVal := valElem.FieldByName(columnName)
if !fieldVal.IsValid() {
if aliasNames == nil {
aliasNames = getAliasNameMap(valElem.Type(), option)
}
// xxx.proto里定义的字段名可能是cfg_id
// 生成的xxx.pb里面的字段名会变成CfgId
// 如果csv里面的列名使用cfg_id也要能解析
if realFieldName, ok := aliasNames[columnName]; ok {
fieldVal = valElem.FieldByName(realFieldName)
}
}
if fieldVal.Kind() == reflect.Ptr { // 指针类型的字段,如 Name *string
fieldObj := reflect.New(fieldVal.Type().Elem()) // 如new(string)
fieldVal.Set(fieldObj) // 如 obj.Name = new(string)
fieldVal = fieldObj.Elem() // 如 *(obj.Name)
}
ConvertStringToFieldValue(val, fieldVal, columnName, fieldString, option, false)
}
return nil
}