diff --git a/docs/api/packages/fileutil.md b/docs/api/packages/fileutil.md index 4caa5e5..a5183d4 100644 --- a/docs/api/packages/fileutil.md +++ b/docs/api/packages/fileutil.md @@ -750,6 +750,10 @@ func main() { 函数签名: ```go +// filepath: CSV文件路径。 +// records: 写入文件的map切片。map值必须为基本类型。会以map键的字母顺序写入。 +// appendToExistingFile: 是否为追加写模式。 +// delimiter: CSV文件分割符。 func WriteMapsToCsv(filepath string, records []map[string]string, append_to_existing_file bool, delimiter ...rune) error ``` @@ -770,23 +774,23 @@ func main() { f, _ := os.OpenFile(fpath, os.O_WRONLY|os.O_TRUNC, 0777) defer f.Close() - records := []map[string]string{ - {"Name": "Lili", "Age": "22", "gender": "female"}, - {"Name": "Jim", "Age": "21", "gender": "male"}, + records := []map[string]any{ + {"Name": "Lili", "Age": "22", "Gender": "female"}, + {"Name": "Jim", "Age": "21", "Gender": "male"}, } - err := WriteMapsToCsv(csvFilePath, records, false, ';') + err := fileutil.WriteMapsToCsv(csvFilePath, records, false, ';') if err != nil { log.Fatal(err) } - content, err := ReadCsvFile(csvFilePath, ';') + content, err := fileutil.ReadCsvFile(csvFilePath, ';') fmt.Println(content) // Output: - // [[Name Age gender] [Lili 22 female] [Jim 21 male]] + // [[Age Gender Name] [22 female Lili] [21 male Jim]] } ``` diff --git a/docs/en/api/packages/fileutil.md b/docs/en/api/packages/fileutil.md index c2c506a..a46be12 100644 --- a/docs/en/api/packages/fileutil.md +++ b/docs/en/api/packages/fileutil.md @@ -751,6 +751,10 @@ func main() { Signature: ```go +// filepath: path of the CSV file. +// records: slice of maps to be written. the value of map should be basic type. The maps will be sorted by key in alphabeta order, then be written into csv file. +// appendToExistingFile: If true, data will be appended to the file if it exists. +// delimiter: Delimiter to use in the CSV file. func WriteMapsToCsv(filepath string, records []map[string]string, append_to_existing_file bool, delimiter ...rune) error ``` @@ -771,23 +775,23 @@ func main() { f, _ := os.OpenFile(fpath, os.O_WRONLY|os.O_TRUNC, 0777) defer f.Close() - records := []map[string]string{ - {"Name": "Lili", "Age": "22", "gender": "female"}, - {"Name": "Jim", "Age": "21", "gender": "male"}, + records := []map[string]any{ + {"Name": "Lili", "Age": "22", "Gender": "female"}, + {"Name": "Jim", "Age": "21", "Gender": "male"}, } - err := WriteMapsToCsv(csvFilePath, records, false, ';') + err := fileutil.WriteMapsToCsv(csvFilePath, records, false, ';') if err != nil { log.Fatal(err) } - content, err := ReadCsvFile(csvFilePath, ';') + content, err := fileutil.ReadCsvFile(csvFilePath, ';') fmt.Println(content) // Output: - // [[Name Age gender] [Lili 22 female] [Jim 21 male]] + // [[Age Gender Name] [22 female Lili] [21 male Jim]] } ``` diff --git a/fileutil/file.go b/fileutil/file.go index d8c428e..a29a2a2 100644 --- a/fileutil/file.go +++ b/fileutil/file.go @@ -20,6 +20,7 @@ import ( "os" "path/filepath" "runtime" + "sort" "strings" "github.com/duke-git/lancet/v2/validator" @@ -754,8 +755,22 @@ func escapeCSVField(field string, delimiter rune) string { // WriteMapsToCsv write slice of map to csv file. // Play: todo -func WriteMapsToCsv(filepath string, records []map[string]string, append_to_existing_file bool, delimiter ...rune) error { - var datas_to_write [][]string +// filepath: Path to the CSV file. +// records: Slice of maps to be written. the value of map should be basic type. +// the maps will be sorted by key in alphabeta order, then be written into csv file. +// appendToExistingFile: If true, data will be appended to the file if it exists. +// delimiter: Delimiter to use in the CSV file. +func WriteMapsToCsv(filepath string, records []map[string]any, appendToExistingFile bool, delimiter ...rune) error { + for _, record := range records { + for _, value := range record { + if !isCsvSupportedType(value) { + return errors.New("unsupported value type detected; only basic types are supported: \nbool, rune, string, int, int64, float32, float64, uint, byte, complex128, complex64, uintptr") + } + } + } + + var datasToWrite [][]string + // 标题(列名) var headers []string if len(records) > 0 { @@ -763,24 +778,44 @@ func WriteMapsToCsv(filepath string, records []map[string]string, append_to_exis headers = append(headers, key) } } + + // sort keys in alphabeta order + sort.Strings(headers) + // 追加模式不重复写字段名 - if !append_to_existing_file { - datas_to_write = append(datas_to_write, headers) + if !appendToExistingFile { + datasToWrite = append(datasToWrite, headers) } - // 写入数据行 + for _, record := range records { var row []string for _, header := range headers { - row = append(row, record[header]) + row = append(row, fmt.Sprintf("%v", record[header])) } - datas_to_write = append(datas_to_write, row) + datasToWrite = append(datasToWrite, row) } - // 提取自定义分隔符 + var sep rune if len(delimiter) > 0 { sep = delimiter[0] } else { sep = ',' } - return WriteCsvFile(filepath, datas_to_write, append_to_existing_file, sep) + + return WriteCsvFile(filepath, datasToWrite, appendToExistingFile, sep) } + +// check if the value of map which to be written into csv is basic type. +func isCsvSupportedType(v interface{}) bool { + switch v.(type) { + case bool, rune, string, int, int64, float32, float64, uint, byte, complex128, complex64, uintptr: + return true + default: + return false + } +} + +// sort map by key in alphabeta order. +// func sortMap(records []map[string]any) []map[string]any { + +// } diff --git a/fileutil/file_example_test.go b/fileutil/file_example_test.go index b48d394..1932831 100644 --- a/fileutil/file_example_test.go +++ b/fileutil/file_example_test.go @@ -3,6 +3,7 @@ package fileutil import ( "fmt" "io" + "log" "os" ) @@ -331,26 +332,26 @@ func ExampleWriteCsvFile() { // [[Lili 22 female] [Jim 21 male]] } -// func ExampleWriteMapsToCsv() { -// csvFilePath := "./testdata/test3.csv" -// records := []map[string]string{ -// {"Name": "Lili", "Age": "22", "gender": "female"}, -// {"Name": "Jim", "Age": "21", "gender": "male"}, -// } +func ExampleWriteMapsToCsv() { + csvFilePath := "./testdata/test3.csv" + records := []map[string]any{ + {"Name": "Lili", "Age": "22", "Gender": "female"}, + {"Name": "Jim", "Age": "21", "Gender": "male"}, + } -// err := WriteMapsToCsv(csvFilePath, records, false, ';') + err := WriteMapsToCsv(csvFilePath, records, false, ';') -// if err != nil { -// log.Fatal(err) -// } + if err != nil { + log.Fatal(err) + } -// content, err := ReadCsvFile(csvFilePath, ';') + content, err := ReadCsvFile(csvFilePath, ';') -// fmt.Println(content) //顺序不固定 + fmt.Println(content) -// // Output: -// // [[Name Age gender] [Lili 22 female] [Jim 21 male]] -// } + // Output: + // [[Age Gender Name] [22 female Lili] [21 male Jim]] +} func ExampleWriteStringToFile() { filepath := "./test.txt" diff --git a/fileutil/file_test.go b/fileutil/file_test.go index 0a00c56..5750670 100644 --- a/fileutil/file_test.go +++ b/fileutil/file_test.go @@ -392,9 +392,9 @@ func TestWriteMapsToCsv(t *testing.T) { assert := internal.NewAssert(t, "TestWriteMapsToCSV") csvFilePath := "./testdata/test4.csv" - records := []map[string]string{ - {"Name": "Lili", "Age": "22", "gender": "female"}, - {"Name": "Jim", "Age": "21", "gender": "male"}, + records := []map[string]any{ + {"Name": "Lili", "Age": "22", "Gender": "female"}, + {"Name": "Jim", "Age": "21", "Gender": "male"}, } err := WriteMapsToCsv(csvFilePath, records, false, ';') @@ -407,7 +407,9 @@ func TestWriteMapsToCsv(t *testing.T) { assert.Equal(3, len(content)) assert.Equal(3, len(content[0])) - // assert.Equal("Lili", content[1][0]) + assert.Equal("22", content[1][0]) + assert.Equal("female", content[1][1]) + assert.Equal("Lili", content[1][2]) } func TestWriteStringToFile(t *testing.T) { diff --git a/fileutil/testdata/test3.csv b/fileutil/testdata/test3.csv index 965779c..aab46b6 100644 --- a/fileutil/testdata/test3.csv +++ b/fileutil/testdata/test3.csv @@ -1,3 +1,3 @@ -Age;gender;Name +Age;Gender;Name 22;female;Lili 21;male;Jim diff --git a/fileutil/testdata/test4.csv b/fileutil/testdata/test4.csv index 875d813..aab46b6 100644 --- a/fileutil/testdata/test4.csv +++ b/fileutil/testdata/test4.csv @@ -1,3 +1,3 @@ -Name;Age;gender -Lili;22;female -Jim;21;male +Age;Gender;Name +22;female;Lili +21;male;Jim