github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/easy/ezmap/csv.go (about) 1 package ezmap 2 3 import ( 4 "bytes" 5 "encoding/csv" 6 "encoding/json" 7 "fmt" 8 "reflect" 9 "sort" 10 "unsafe" 11 ) 12 13 // MarshalCSV marshal map[string]any records to CSV encoding. 14 // It uses the std encoding/csv.Writer with its default settings for csv encoding. 15 // If length of records is zero, it returns (nil, nil). 16 // 17 // Caller should guarantee that every record have same schema. 18 // The keys of the first item in records is used as the result CSV header, 19 // for the left items in records, if a key is missing, it is ignored, 20 // keys not present in the first item are simply ignored. 21 func MarshalCSV[T ~map[string]any](records []T) ([]byte, error) { 22 if len(records) == 0 { 23 return nil, nil 24 } 25 26 header := make([]string, 0, len(records[0])) 27 for k := range records[0] { 28 header = append(header, k) 29 } 30 sort.Strings(header) 31 32 var err error 33 var buf bytes.Buffer 34 w := csv.NewWriter(&buf) 35 if err = w.Write(header); err != nil { 36 return nil, err 37 } 38 39 var strRecord []string 40 for _, r := range records { 41 strRecord = strRecord[:0] 42 for _, k := range header { 43 v, ok := r[k] 44 if !ok { 45 strRecord = append(strRecord, "") 46 continue 47 } 48 var valueStr string 49 kind := reflect.TypeOf(v).Kind() 50 if kind == reflect.String { 51 valueStr = castStr(v) 52 } else if isSimpleType(kind) { 53 valueStr = fmt.Sprint(v) 54 } else { 55 valueStr, err = toJSON(v) 56 if err != nil { 57 return nil, err 58 } 59 } 60 strRecord = append(strRecord, valueStr) 61 } 62 if err = w.Write(strRecord); err != nil { 63 return nil, err 64 } 65 } 66 w.Flush() 67 if err = w.Error(); err != nil { 68 return nil, err 69 } 70 return buf.Bytes(), nil 71 } 72 73 // UnmarshalCVS parses CSV-encoded data to map[string]any records. 74 // It uses the std encoding/csv.Reader with its default settings for csv encoding. 75 // The first record parsed from the first row is treated as CSV header, 76 // and used as the result map keys. 77 func UnmarshalCVS(data []byte) ([]Map, error) { 78 csvReader := csv.NewReader(bytes.NewReader(data)) 79 records, err := csvReader.ReadAll() 80 if err != nil { 81 return nil, fmt.Errorf("csv.Reader.ReadAll: %w", err) 82 } 83 if len(records) <= 1 { 84 return nil, nil 85 } 86 header, records := records[0], records[1:] 87 for i, x := range header { 88 for j := i + 1; j < len(header); j++ { 89 if x == header[j] { 90 return nil, fmt.Errorf("duplicate header: %s", x) 91 } 92 } 93 } 94 out := make([]Map, 0, len(records)) 95 for _, record := range records { 96 m := make(Map, len(header)) 97 for i, x := range record { 98 m[header[i]] = x 99 } 100 out = append(out, m) 101 } 102 return out, nil 103 } 104 105 func isSimpleType(kind reflect.Kind) bool { 106 switch kind { 107 case reflect.Bool, 108 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 109 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 110 reflect.Float32, reflect.Float64, 111 reflect.String: 112 return true 113 default: 114 return false 115 } 116 } 117 118 func toJSON(v any) (string, error) { 119 buf, err := json.Marshal(v) 120 if err != nil { 121 return "", fmt.Errorf("cannot marshal value of type %T to JSON: %w", v, err) 122 } 123 return string(buf), nil 124 } 125 126 func castStr(v any) string { 127 // type eface struct { rtype unsafe.Pointer, word unsafe.Pointer } 128 return *(*string)((*[2]unsafe.Pointer)(unsafe.Pointer(&v))[1]) 129 }