こういう CSV があるとします。ただし、BOM付きのUTF-8。

"名前","個数"
"りんご",1
"みかん",2

これを csv.Reader で読み込んでやる。

package main

func main() {
	f, err := os.Open("fruits.csv")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	r := csv.NewReader(f)
	for {
		rec, err := r.Read()
		if err != nil {
			log.Fatal(err)
		}
		// Do something.
	}
}

普通ならこれでいいんだけど、BOM付きのUTF8の場合 line 1, column 1: bare " in non-quoted-field というエラーになる。 とりあえず r.LazyQuotes = true とすればエラーは出ないのだが、最初の要素が "名前" とクオート付きになる。 というかクオート付くだけならまだしも、実は BOM が付いているので rec[0] == "\""名前"\""false になる。

Golang の csv パッケージは UTF8 の BOM には対応していなくて、対応するつもりもなさそうだ。

仕方ないので自分で対応する。 とりあえず、UTF8かつBOMがあれば読み飛ばせばよかろうなので、

func newCsvReader(r io.Reader) *csv.Reader {
	br := bufio.NewReader(r)
	bs, err := br.Peek(3)
	if err != nil {
		return csv.NewReader(br)
	}
	if bs[0] == 0xEF && bs[1] == 0xBB && bs[2] == 0xBF {
		br.Discard(3)
	}
	return csv.NewReader(br)
}

こんな感じで動く。

逆にBOM付きで書き込みたいなら、予め BOM を出力してやればいいのだから

func newCsvWriter(w io.Writer, bom bool) *csv.Writer {
	bw := bufio.NewWriter(w)
	if bom {
		bw.Write([]byte{0xEF, 0xBB, 0xBF})
	}
	return csv.NewWriter(bw)
}

こんな感じで動く。

とまあ、そんな経由で pinzolo/csvutil: CSV Utility for Golang というのを作ったので、使うなりツッコミ入れるなり育てるなりよろしくお願いします。

NewReaderWithEncNewWriterWithEnc は たった一行のコードなんだから都度書けよ、大してタイピング数変わらねーだろと思われるかもしれませんが、SHIFT_JIS な CSV を読み込んだり出力する時、どっちが Encoder でどっちが Decoder かよく忘れてしまうんです。

しかしまぁ、UTF8のBOMで幸せになった話を聞いたことが無い。