RubyからCSVファイルを読み込む時にCSV.tableメソッドが大変便利で愛用していたのですが、
データによっては正しく読み込めないことがあったので原因と対処をまとめます。
起きた問題
以下のようなCSVをCSV.tableで読み込んだところ、emp_noが正しく読み込まれませんでした。
emp_no, name
010, 栗山英樹
csv = CSV.table('./sample.csv', {:encoding => 'UTF-8'})
csv[:emp_no][0] #=> 8
勘の良い方ならおわかりかと思いますが、'010'という文字列が8進数の数値だと思われてしまっているようです。
原因
なんでこんなことになるかわからないので、CSV.tableのソースを見てみたら以下のようになっていました。
#
# A shortcut for:
#
# CSV.read( path, { headers: true,
# converters: :numeric,
# header_converters: :symbol }.merge(options) )
#
def self.table(path, options = Hash.new)
read( path, { headers: true,
converters: :numeric,
header_converters: :symbol }.merge(options) )
end
要はCSV.tableは、より一般的なCSV.readに良い感じにオプションを渡しているだけのラッパーなんですね。
で、問題になるのはこのconverters: :numeric
の部分です。
リファレンスマニュアルのcovertersの部分を読むと以下のような記述が
:integer
Kernel.#Integer を使用してフィールドを変換します。
:float
Kernel.#Float を使用してフィールドを変換します。
:numeric
:integer と :float の組み合わせです。
Kernel.Integer
は数値と判断できる文字列を整数に変換しますので、
"010"は8進数だと判断されて数値の8に変換されてしまっていた、ということです。
対処法
convertersにnumericを使わないようにオプションを指定してあげます。
csv = CSV.table('sample.csv', {:encoding => 'UTF-8', :converters => nil})
これで直りました。