いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-631 (csv処理で列が不揃いでもエラーにしない)(csv.Reader.FieldsPerRecord)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

encoding/csvパッケージを利用してCSV処理を書く際に、たまに列が不揃いの場合でも読み取ってほしいときがあります。

そのような場合は、csv.Reader.FieldsPerRecordに「負の値」を設定するとエラーにならずに読み取りを行ってくれます。

csv.Reader.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 int

サンプル

package csvop

import (
    "bytes"
    "encoding/csv"
    "fmt"
)

// FieldsPerRecord は、csv.Reader.FieldsPerRecordのサンプルです。
//
// csv.Reader.FieldsPerRecord は設定する値によって挙動が変わる。
//
//   - 「0より大きな正の値」を設定すると、その列数でない場合 *csv.ParseError が発生する
//   - 「0」を指定すると、最初の行の列数を基準として解析し、その列数でない場合 *csv.ParseError が発生する
//   - 「負の値」 を設定すると列が不揃いでもエラーにならない
//
// REFERENCES:
//   - https://pkg.go.dev/encoding/csv@go1.25.3#Reader.FieldsPerRecord
func FieldsPerRecord() error {
    var (
        data []byte
    )
    data = fmt.Appendln(data, "hello,world")
    data = fmt.Appendln(data, "world,hello")
    data = fmt.Appendln(data, "HELLO,WORLD,999") // ここだけ列が不揃い

    //
    // FieldsPerRecordの値が「0より大きな正の値」
    // (指定した列数で無い場合エラーとなる)
    //
    {
        var (
            buf    = bytes.NewBuffer(data)
            reader = csv.NewReader(buf)
            record []string
            err    error
        )
        reader.FieldsPerRecord = 2

        for i := 0; ; i++ {
            if record, err = reader.Read(); err != nil {
                fmt.Printf("[0より大きな正の値] [%d] %T: %s\n", i, err, err)
                break
            }

            fmt.Printf("[0より大きな正の値] [%d] %v\n", i, record)
        }
    }

    //
    // FieldsPerRecordの値が「0」
    // (先頭レコードの列数を基準として処理する)
    //
    {
        var (
            buf    = bytes.NewBuffer(data)
            reader = csv.NewReader(buf)
            record []string
            err    error
        )
        reader.FieldsPerRecord = 0

        for i := 0; ; i++ {
            if record, err = reader.Read(); err != nil {
                fmt.Printf("[0        ] [%d] %T: %s\n", i, err, err)
                break
            }

            fmt.Printf("[0        ] [%d] %v\n", i, record)
        }
    }

    //
    // FieldsPerRecordの値が「負の値」
    // (列数が不揃いでもエラーとならない)
    //
    {
        var (
            buf    = bytes.NewBuffer(data)
            reader = csv.NewReader(buf)
            record []string
            err    error
        )
        reader.FieldsPerRecord = -1

        for i := 0; ; i++ {
            if record, err = reader.Read(); err != nil {
                fmt.Printf("[負の値      ] [%d] %T: %s\n", i, err, err)
                break
            }

            fmt.Printf("[負の値      ] [%d] %v\n", i, record)
        }
    }

    return nil
}

実行

       $ task
        task: [run] ./try-golang -onetime

        ENTER EXAMPLE NAME: csv_fields

        [Name] "csv_fieldsperrecord"
        [0より大きな正の値] [0] [hello world]
        [0より大きな正の値] [1] [world hello]
        [0より大きな正の値] [2] *csv.ParseError: record on line 3: wrong number of fields
        [0        ] [0] [hello world]
        [0        ] [1] [world hello]
        [0        ] [2] *csv.ParseError: record on line 3: wrong number of fields
        [負の値      ] [0] [hello world]
        [負の値      ] [1] [world hello]
        [負の値      ] [2] [HELLO WORLD 999]
        [負の値      ] [3] *errors.errorString: EOF


        [Elapsed] 352.396µs

参考情報

個人的Goのおすすめ書籍

個人的に読んでとても勉強になった書籍さんたちです。


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。