RubyでBOMつきUTF-8のCSVを正しく読み込む方法3選
問題:CSV.readでは列情報を正しく読み込むことができなかった
RubyでCSVファイルを読み込む際はDefault Gemsに含まれている[1]csv gemを使うことが多いでしょう。
BOMつきのUTF-8エンコードされたCSVファイルを読み込むと、列情報を正しく参照できない問題に直面しました。
例えば、以下のようなBOMつきUTF-8のCSVファイルがあるとします。
name,age
Tom,20
Jerry,22
Alice,25
このCSVファイルを以下のようにCSV.readで読み込むと、name列の情報を取得することができませんでした。
# 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オブジェクトが返ってきます。
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]
紹介するサンプルコードは以下でも参照できます。
1. CSV.readでencoding: 'bom|utf-8'を使って読み込む
まずはじめはCSV.readメソッドの呼び出しにencoding: 'bom|utf-8'オプションを指定する方法です。
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.readでheader_converters: :symbolを指定して読み込む
2つ目の方法はCSV.readメソッドの呼び出しにheader_converters: :symbolオプションを指定する方法です。
このオプションを指定すると、ヘッダーを読み込む際にシンボルへ変換してくれます。
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ファイルを読み込む方法です。
このメソッドでは2つ目の方法と同様に、ヘッダーのキーがシンボルに変換されます。
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になっていることに気づかずハマってしまいました。
みなさんもファイルのエンコーディングには十分ご注意くださいませ。
-
Ruby3.4からはDefault GemsではなくBundled Gemに変更されます。 (https://www.ruby-lang.org/en/news/2023/12/25/ruby-3-3-0-released/) ↩︎
Discussion
CSV::Tableの方がメモリを食うので注意が必要そうコメントありがとうございます!
メモリ使用量に関して考慮できていませんでした。
おっしゃるとおり、データ量が多い場合は
CSV.readで読み込んだほうが安全ですね。