🦔

RubyでBOMつきUTF-8のCSVを正しく読み込む方法3選

2024/11/20に公開

問題:CSV.readでは列情報を正しく読み込むことができなかった

RubyでCSVファイルを読み込む際はDefault Gemsに含まれている[1]csv gemを使うことが多いでしょう。

BOMつきのUTF-8エンコードされたCSVファイルを読み込むと、列情報を正しく参照できない問題に直面しました。

例えば、以下のようなBOMつきUTF-8のCSVファイルがあるとします。

utf8_with_bom.csv
name,age
Tom,20
Jerry,22
Alice,25

このCSVファイルを以下のようにCSV.readで読み込むと、name列の情報を取得することができませんでした。

faild_to_read_csv_with_bom.rb
# frozen_string_literal: true

require 'csv'

csv = CSV.read('utf8_with_bom.csv', headers: true)

puts "name:"
puts csv['name']
puts "age:"
puts csv['age']

# Outputs:
# name:
#
#
#
# age:
# 20
# 22
# 25

CSV.readメソッドにheaders: trueを追加すると、CSV.Tableオブジェクトが返ってきます。
https://docs.ruby-lang.org/ja/latest/method/CSV/s/read.html

CSV.Tableクラスでは[]メソッドにインデックス番号を渡すとインデックス番号に対応する行情報を参照し、ヘッダー名を指定するとそのヘッダーの列情報を参照します。
よって、csv['name']とすればname列の情報を取得できるはずですが、すべてnilになってしまいました。

その原因はCSVファイルのエンコーディングがBOMつきUTF-8になっていたことでした。

正しく読み込む方法3選

BOMつきUTF-8を読み込む方法を3つ紹介します。
もちろん方法は3つだけではありませんが、実務で使えそうな方法をまとめてみました。

Ruby 3.3.4の環境で検証しました。

❯ ruby -v
ruby 3.3.4 (2024-07-09 revision be1089c8ec) [arm64-darwin23]

紹介するサンプルコードは以下でも参照できます。

https://github.com/yuma-ito-bd/zenn-articles/tree/997ccda4eab3b4b8c7183c5f53f3de874e64daaa/scripts/20241120-ruby-csv-utf8-with-bom

1. CSV.readencoding: 'bom|utf-8'を使って読み込む

まずはじめはCSV.readメソッドの呼び出しにencoding: 'bom|utf-8'オプションを指定する方法です。

using_csv_read_with_encoding_option.rb
csv = CSV.read('utf8_with_bom.csv', encoding: 'bom|utf-8', headers: true)
puts "name:"
puts csv['name']

# Outputs:
# name:
# Tom
# Jerry
# Alice

encodingを指定することにより、name列を正しく参照することができました。

2. CSV.readheader_converters: :symbolを指定して読み込む

2つ目の方法はCSV.readメソッドの呼び出しにheader_converters: :symbolオプションを指定する方法です。
このオプションを指定すると、ヘッダーを読み込む際にシンボルへ変換してくれます。
https://docs.ruby-lang.org/ja/latest/method/CSV/s/new.html

using_header_converters.rb
csv = CSV.read('utf8_with_bom.csv', headers: true, header_converters: :symbol)
puts "name:"
puts csv[:name]

# Outputs:
# name:
# Tom
# Jerry
# Alice

よって、列情報を参照する際はシンボルでヘッダー名を指定する必要があります。
この方法のメリットとしては、読み込むファイルがBOMのありorなしに関係なく適用できる点です。
ちなみに、日本語のヘッダー名でもシンボルで指定可能です。(例::名前

3. CSV.tableを使って読み込む

最後の方法は、CSV.tableメソッドを使ってCSVファイルを読み込む方法です。
https://docs.ruby-lang.org/ja/latest/method/CSV/s/table.html

このメソッドでは2つ目の方法と同様に、ヘッダーのキーがシンボルに変換されます。

using_csv_table_method.rb
csv = CSV.table('utf8_with_bom.csv')
puts "name:"
puts csv[:name]

# Outputs:
# name:
# Tom
# Jerry
# Alice

この方法が一番シンプルかと思います。

ただし、CSV.tableメソッドの中身はCSV.readに以下のオプションを指定していることと同等です。
2つ目の方法にconverters: :numericが追加されているのでご注意ください。

CSV.read( path, { headers:           true,
                  converters:        :numeric,
                  header_converters: :symbol }.merge(options) )

まとめ

BOMつきUTF-8のCSVファイルを正しく読み込む方法を3つご紹介しました。
個人的には一番シンプルに書けるCSV.tableが良さそうに思います。

ExcelファイルからCSVに変換した際、BOMつきUTF-8になっていることに気づかずハマってしまいました。
みなさんもファイルのエンコーディングには十分ご注意くださいませ。

脚注
  1. Ruby3.4からはDefault GemsではなくBundled Gemに変更されます。 (https://www.ruby-lang.org/en/news/2023/12/25/ruby-3-3-0-released/) ↩︎

GitHubで編集を提案

Discussion