mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-13 17:22:27 +08:00
fix: fix the map key order issue of WriteMapsToCsv
This commit is contained in:
@@ -750,6 +750,10 @@ func main() {
|
|||||||
<b>函数签名:</b>
|
<b>函数签名:</b>
|
||||||
|
|
||||||
```go
|
```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
|
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)
|
f, _ := os.OpenFile(fpath, os.O_WRONLY|os.O_TRUNC, 0777)
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
records := []map[string]string{
|
records := []map[string]any{
|
||||||
{"Name": "Lili", "Age": "22", "gender": "female"},
|
{"Name": "Lili", "Age": "22", "Gender": "female"},
|
||||||
{"Name": "Jim", "Age": "21", "gender": "male"},
|
{"Name": "Jim", "Age": "21", "Gender": "male"},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := WriteMapsToCsv(csvFilePath, records, false, ';')
|
err := fileutil.WriteMapsToCsv(csvFilePath, records, false, ';')
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := ReadCsvFile(csvFilePath, ';')
|
content, err := fileutil.ReadCsvFile(csvFilePath, ';')
|
||||||
|
|
||||||
fmt.Println(content)
|
fmt.Println(content)
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// [[Name Age gender] [Lili 22 female] [Jim 21 male]]
|
// [[Age Gender Name] [22 female Lili] [21 male Jim]]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -751,6 +751,10 @@ func main() {
|
|||||||
<b>Signature:</b>
|
<b>Signature:</b>
|
||||||
|
|
||||||
```go
|
```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
|
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)
|
f, _ := os.OpenFile(fpath, os.O_WRONLY|os.O_TRUNC, 0777)
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
records := []map[string]string{
|
records := []map[string]any{
|
||||||
{"Name": "Lili", "Age": "22", "gender": "female"},
|
{"Name": "Lili", "Age": "22", "Gender": "female"},
|
||||||
{"Name": "Jim", "Age": "21", "gender": "male"},
|
{"Name": "Jim", "Age": "21", "Gender": "male"},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := WriteMapsToCsv(csvFilePath, records, false, ';')
|
err := fileutil.WriteMapsToCsv(csvFilePath, records, false, ';')
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := ReadCsvFile(csvFilePath, ';')
|
content, err := fileutil.ReadCsvFile(csvFilePath, ';')
|
||||||
|
|
||||||
fmt.Println(content)
|
fmt.Println(content)
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// [[Name Age gender] [Lili 22 female] [Jim 21 male]]
|
// [[Age Gender Name] [22 female Lili] [21 male Jim]]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/duke-git/lancet/v2/validator"
|
"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.
|
// WriteMapsToCsv write slice of map to csv file.
|
||||||
// Play: todo
|
// Play: todo
|
||||||
func WriteMapsToCsv(filepath string, records []map[string]string, append_to_existing_file bool, delimiter ...rune) error {
|
// filepath: Path to the CSV file.
|
||||||
var datas_to_write [][]string
|
// 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
|
var headers []string
|
||||||
if len(records) > 0 {
|
if len(records) > 0 {
|
||||||
@@ -763,24 +778,44 @@ func WriteMapsToCsv(filepath string, records []map[string]string, append_to_exis
|
|||||||
headers = append(headers, key)
|
headers = append(headers, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sort keys in alphabeta order
|
||||||
|
sort.Strings(headers)
|
||||||
|
|
||||||
// 追加模式不重复写字段名
|
// 追加模式不重复写字段名
|
||||||
if !append_to_existing_file {
|
if !appendToExistingFile {
|
||||||
datas_to_write = append(datas_to_write, headers)
|
datasToWrite = append(datasToWrite, headers)
|
||||||
}
|
}
|
||||||
// 写入数据行
|
|
||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
var row []string
|
var row []string
|
||||||
for _, header := range headers {
|
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
|
var sep rune
|
||||||
if len(delimiter) > 0 {
|
if len(delimiter) > 0 {
|
||||||
sep = delimiter[0]
|
sep = delimiter[0]
|
||||||
} else {
|
} else {
|
||||||
sep = ','
|
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 {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package fileutil
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -331,26 +332,26 @@ func ExampleWriteCsvFile() {
|
|||||||
// [[Lili 22 female] [Jim 21 male]]
|
// [[Lili 22 female] [Jim 21 male]]
|
||||||
}
|
}
|
||||||
|
|
||||||
// func ExampleWriteMapsToCsv() {
|
func ExampleWriteMapsToCsv() {
|
||||||
// csvFilePath := "./testdata/test3.csv"
|
csvFilePath := "./testdata/test3.csv"
|
||||||
// records := []map[string]string{
|
records := []map[string]any{
|
||||||
// {"Name": "Lili", "Age": "22", "gender": "female"},
|
{"Name": "Lili", "Age": "22", "Gender": "female"},
|
||||||
// {"Name": "Jim", "Age": "21", "gender": "male"},
|
{"Name": "Jim", "Age": "21", "Gender": "male"},
|
||||||
// }
|
}
|
||||||
|
|
||||||
// err := WriteMapsToCsv(csvFilePath, records, false, ';')
|
err := WriteMapsToCsv(csvFilePath, records, false, ';')
|
||||||
|
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// log.Fatal(err)
|
log.Fatal(err)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// content, err := ReadCsvFile(csvFilePath, ';')
|
content, err := ReadCsvFile(csvFilePath, ';')
|
||||||
|
|
||||||
// fmt.Println(content) //顺序不固定
|
fmt.Println(content)
|
||||||
|
|
||||||
// // Output:
|
// Output:
|
||||||
// // [[Name Age gender] [Lili 22 female] [Jim 21 male]]
|
// [[Age Gender Name] [22 female Lili] [21 male Jim]]
|
||||||
// }
|
}
|
||||||
|
|
||||||
func ExampleWriteStringToFile() {
|
func ExampleWriteStringToFile() {
|
||||||
filepath := "./test.txt"
|
filepath := "./test.txt"
|
||||||
|
|||||||
@@ -392,9 +392,9 @@ func TestWriteMapsToCsv(t *testing.T) {
|
|||||||
assert := internal.NewAssert(t, "TestWriteMapsToCSV")
|
assert := internal.NewAssert(t, "TestWriteMapsToCSV")
|
||||||
|
|
||||||
csvFilePath := "./testdata/test4.csv"
|
csvFilePath := "./testdata/test4.csv"
|
||||||
records := []map[string]string{
|
records := []map[string]any{
|
||||||
{"Name": "Lili", "Age": "22", "gender": "female"},
|
{"Name": "Lili", "Age": "22", "Gender": "female"},
|
||||||
{"Name": "Jim", "Age": "21", "gender": "male"},
|
{"Name": "Jim", "Age": "21", "Gender": "male"},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := WriteMapsToCsv(csvFilePath, records, false, ';')
|
err := WriteMapsToCsv(csvFilePath, records, false, ';')
|
||||||
@@ -407,7 +407,9 @@ func TestWriteMapsToCsv(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(3, len(content))
|
assert.Equal(3, len(content))
|
||||||
assert.Equal(3, len(content[0]))
|
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) {
|
func TestWriteStringToFile(t *testing.T) {
|
||||||
|
|||||||
2
fileutil/testdata/test3.csv
vendored
2
fileutil/testdata/test3.csv
vendored
@@ -1,3 +1,3 @@
|
|||||||
Age;gender;Name
|
Age;Gender;Name
|
||||||
22;female;Lili
|
22;female;Lili
|
||||||
21;male;Jim
|
21;male;Jim
|
||||||
|
|||||||
|
6
fileutil/testdata/test4.csv
vendored
6
fileutil/testdata/test4.csv
vendored
@@ -1,3 +1,3 @@
|
|||||||
Name;Age;gender
|
Age;Gender;Name
|
||||||
Lili;22;female
|
22;female;Lili
|
||||||
Jim;21;male
|
21;male;Jim
|
||||||
|
|||||||
|
Reference in New Issue
Block a user