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  }