CSV 格式说明和应用
问题
我们常常将多个字符串item使用逗号拼接成一个字符串,用来表示数组,使用时再用逗号切割成为数组。比如安卓机型列表:
ALN-AL10,ALN-AL10,BRA-AL00,ALN-AL00/ALN-AL80
直到有一天,苹果设备也要用到这个机型列表,而它的每个机型都带着逗号,那我们使用逗号切割就得到了错误的数据。
iPhone15: iPhone15,4
iPhone15Plus: iPhone15,5
iPhone15Pro: iPhone16,1
iPhone15Pro_Max: iPhone16,2
为了解决这个问题,首先想到了换一个分隔符,比如 | ,再比如用一些不可见字符 : 0x01。
但我们不能保证这些字符串 item 一定不包含这些特殊字符,也许还有更好的方法。
既然是逗号分隔,首先想到的就是 CSV格式,毕竟 CSV 的全称就是Comma-Separated Values逗号分隔值。它是如何解决这个问题的?
CSV格式
CSV 的RFC说明文档:https://datatracker.ietf.org/doc/html/rfc4180
- 基本字段
 - 包含逗号的字段,则使用双引号括起来;
 - 包含双引号的字段,则在双引号前面必须加上另一个双引号进行转义。;
 - 包含换行符的字段,则使用双引号括起来;
 - 包含特殊字符的组合的字段,也是使用双引号括起来;
 
姓名,年龄,城市,备注
张三,30,北京,无备注
李四,25,上海,"喜欢, 打篮球"
王五,28,"广州, 广东",""
"李, 六",35,"""特别"" 市","这是一段
跨行的备注"
"陈, 七","40","深圳",
"包含""双引号""和,逗号"

使用 csv 的工具包是可以非常方便的处理这种数据。类似的后台表格导出 csv 文件也应当使用该csv工具包。
example:
Go 语言可以使用 https://pkg.go.dev/encoding/csv
func main() {
    in := `姓名,年龄,城市,备注
张三,30,北京,无备注
李四,25,上海,"喜欢, 打篮球"
王五,28,"广州, 广东",""
"李, 六",35,"""特别"" 市","这是一段
跨行的备注"
"陈, 七","40","深圳","包含""双引号""和,逗号"
`
    r := csv.NewReader(strings.NewReader(in))
    for {
        record, err := r.Read()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(record)
    }
}
输出:
[姓名 年龄 城市 备注]
[张三 30 北京 无备注]
[李四 25 上海 喜欢, 打篮球]
[王五 28 广州, 广东 ]
[李, 六 35 "特别" 市 这是一段
跨行的备注]
[陈, 七 40 深圳 包含"双引号"和,逗号]
package utils
import (
	`bytes`
	`encoding/csv`
	`strings`
)
// SliceToCsvString 将字符串切片转换为CSV字符串
func SliceToCsvString(slice []string) (string, error) {
	var buf bytes.Buffer
	writer := csv.NewWriter(&buf)
	// 写入单行数据
	err := writer.Write(slice)
	if err != nil {
		return "", err
	}
	// 确保所有数据都被写入
	writer.Flush()
	// 检查是否有任何错误
	if err := writer.Error(); err != nil {
		return "", err
	}
	return buf.String(), nil
}
// CsvStringToSlice 将CSV字符串转换为字符串切片
func CsvStringToSlice(csvString string) ([]string, error) {
	reader := csv.NewReader(strings.NewReader(csvString))
	reader.ReuseRecord = true
	// 读取所有记录
	records, err := reader.Read()
	if err != nil {
		return nil, err
	}
	return records, nil
}