📂

【Go】CSVを読み込む際にフィールド数が合わないときの対処法

に公開

はじめに

業務でCSVファイルを読み込んで、様々な処理をすることは多いと思います。
しかし、CSVファイルはいつでも完璧な形をとっているとは限りません。
今回は、フィールド数が揃っていないCSVファイルに対しどう対処できるのかを見ていきたいと思います。

この記事でわかること

  • フィールド数が揃っていないCSVファイルを読み込むときに起きること
  • フィールド数が揃っていないCSVファイルを読み込めるようにするためにできること

前提

今回は、以下のようなユーザー情報が出力されているCSVファイルを用意しました。
前述の通り、フィールド数が揃っていないものとなっております。

csvファイル
1,Alice,24,New York
2,Bob,30
3,Charlie,Los Angeles
4,David

CSVファイルを読み込んでみる

GoでCSVファイルを読み込むためには、以下のステップを踏む必要があります。

1. CSVファイルを開く
2. CSVリーダーを生成
3. CSVファイルを読み込む

このうち、「CSVファイルを読み込む」には、一行ずつ読み込む(Read())か全行まとめて読み込む(ReadAll())かを選択することができます。ただ、全行読み込む場合はメモリ消費量が増えてしまうので、基本は逐次処理する方が良いかと思います。

これを踏まえて、以下のように実装することができます。

main.go
func main() {
    // 1. CSVファイルを開く
    f, err := os.Open("example.csv")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    
    // 2. CSVリーダーの生成
    r := csv.NewReader(f)

    // 3. CSVファイルを読み込む
    for {
        row, err := r.Read()
        if err == io.EOF {
          break
        }
        if err != nil {
          log.Fatal(err)
        }
        fmt.Println(row)
  }
}

これを実行すると以下のような結果となります。

ターミナル
# go run main.go

[1 Alice 24 New York]
2025/11/02 11:07:34 record on line 2: wrong number of fields
exit status 1

1行目は読み込むことができましたが、2行目でエラーになっていることがわかります。

原因

wrong number of fieldsというエラーを訳すと、フィールドの数が間違っているよと言った意味になります。前述の通り、フィールド数が揃っていないのでエラーが起きています。

実は、Read()には以下のような説明が書かれています。

If the record has an unexpected number of fields, Read returns the record along with the error ErrFieldCount.

「フィールド数が想定しているものと異なる場合、レコードとともにErrFieldCountというエラーを返す」ということが書かれています。このErrFieldCountは以下のように定義されています。

reader.go
ErrFieldCount = errors.New("wrong number of fields")

ターミナルに出力されたエラーメッセージが定数定義されており、フィールド数に問題がある場合に読み込むことができたレコードとともに返されるものとなります。

解決

しかし、冒頭にも記載した通り、CSVファイルはいつも完璧な状態であるとは限らないので、そのような場合でも処理を継続する必要が出てくることもあります。
そうした場合、Goでは以下のように解消することができます。

main.go
func main() {
    f, err := os.Open("example.csv")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    
    r := csv.NewReader(f)
    // 追加
    r.FieldsPerRecord = -1
    for {
    row, err := r.Read()
    if err == io.EOF {
      break
    }
    if err != nil {
      log.Fatal(err)
    }
    fmt.Println(row)
    }
}

先ほどのコードに、r.FieldsPerRecord = -1 という処理を追加しました。
FieldsPerRecordについては、パッケージに以下のようなコメントが記載されています。

// FieldsPerRecord is the number of expected fields per record.
// If FieldsPerRecord is positive, Read requires each record to
// have the given number of fields. If FieldsPerRecord is 0, Read sets it to
// the number of fields in the first record, so that future records must
// have the same field count. If FieldsPerRecord is negative, no check is
// made and records may have a variable number of fields.

FieldsPerRecordとは、レコードごとに期待されているフィールド数を表しています。

  • 正の数の場合、指定されたレコード数を持つことを要求
  • 0の場合、最初のレコードに存在するフィールド数を設定し、以降のレコードも同じフィールド数になることを要求
  • 負の数の場合、フィールド数のチェックはされず、可変長のフィールドを設定可能

今回の場合、フィールド数が揃っていないCSVファイルを扱いたいので、負の数を設定する必要があります(-1と指定するのが一般的のようです)。

実行すると、以下のように出力されます。

ターミナル
# go run main.go

[1 Alice 24 New York]
[2 Bob 30]
[3 Charlie Los Angeles]
[4 David]

無事に読み込むことができました!

全行読み込む場合

ReadAll()を使って全行読み込む場合も、同じようにFieldsPerRecordを設定する必要があります。

main.go
func main() {
    f, err := os.Open("example.csv")
	if err != nil {
		log.Fatal(err)
	}
    defer f.Close()

    r := csv.NewReader(f)
    r.FieldsPerRecord = -1
    // 全行読み込む
    rows, err := r.ReadAll()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(rows)
}

実行すると、以下のように出力することができます。

ターミナル
# go run main.go

[[1 Alice 24 New York] [2 Bob 30] [3 Charlie Los Angeles] [4 David]]

まとめ

今回は、フィールド数が揃っていないCSVファイルを読み込むときの対処法を見ていきました。
フィールド数が揃っていない場合、wrong number of fieldsというエラーが起きます。
FieldsPerRecordを負の数に設定することで、可変長のCSVファイルを扱うことができます。

参考

Discussion