Parse CSV file with header fields as attributes for each row

99,828

Solution 1

Using Ruby 1.9 and above, you can get a an indexable object:

CSV.foreach('my_file.csv', :headers => true) do |row|
  puts row['foo'] # prints 1 the 1st time, "blah" 2nd time, etc
  puts row['bar'] # prints 2 the first time, 7 the 2nd time, etc
end

It's not dot syntax but it is much nicer to work with than numeric indexes.

As an aside, for Ruby 1.8.x FasterCSV is what you need to use the above syntax.

Solution 2

Here is an example of the symbolic syntax using Ruby 1.9. In the examples below, the code reads a CSV file named data.csv from Rails db directory.

:headers => true treats the first row as a header instead of a data row. :header_converters => :symbolize parameter then converts each cell in the header row into Ruby symbol.

CSV.foreach("#{Rails.root}/db/data.csv", {:headers => true, :header_converters => :symbol}) do |row|
  puts "#{row[:foo]},#{row[:bar]},#{row[:baz]}"
end

In Ruby 1.8:

require 'fastercsv'
CSV.foreach("#{Rails.root}/db/data.csv", {:headers => true, :header_converters => :symbol}) do |row|
  puts "#{row[:foo]},#{row[:bar]},#{row[:baz]}"
end

Based on the CSV provided by the Poul (the StackOverflow asker), the output from the example code above will be:

1,2,3
blah,7,blam
4,5,6

Depending on the characters used in the headers of the CSV file, it may be necessary to output the headers in order to see how CSV (FasterCSV) converted the string headers to symbols. You can output the array of headers from within the CSV.foreach.

row.headers

Solution 3

Easy to get a hash in Ruby 2.3:

CSV.foreach('my_file.csv', headers: true, header_converters: :symbol) do |row|
  puts row.to_h[:foo]
  puts row.to_h[:bar]
end

Solution 4

Although I am pretty late to the discussion, a few months ago I started a "CSV to object mapper" at https://github.com/vicentereig/virgola.

Given your CSV contents, mapping them to an array of FooBar objects is pretty straightforward:

"foo","bar","baz"
1,2,3
"blah",7,"blam"
4,5,6
require 'virgola'

class FooBar
  include Virgola

  attribute :foo
  attribute :bar
  attribute :baz
end

csv = <<CSV
"foo","bar","baz"
1,2,3
"blah",7,"blam"
4,5,6
CSV

foo_bars = FooBar.parse(csv).all
foo_bars.each { |foo_bar| puts foo_bar.foo, foo_bar.bar, foo_bar.baz }

Solution 5

Since I hit this question with some frequency:

array_of_hashmaps = CSV.read("path/to/file.csv", headers: true)
puts array_of_hashmaps.first["foo"] # 1

This is the non-block version, when you want to slurp the whole file.

Share:
99,828
Poul
Author by

Poul

Chief Technology Officer at Athena Capital Research

Updated on July 05, 2022

Comments

  • Poul
    Poul almost 2 years

    I would like to parse a CSV file so that each row is treated like an object with the header-row being the names of the attributes in the object. I could write this, but I'm sure its already out there.

    Here is my CSV input:

    "foo","bar","baz"
    1,2,3
    "blah",7,"blam"
    4,5,6
    

    The code would look something like this:

    CSV.open('my_file.csv','r') do |csv_obj|
      puts csv_obj.foo   #prints 1 the 1st time, "blah" 2nd time, etc
      puts csv.bar       #prints 2 the first time, 7 the 2nd time, etc
    end
    

    With Ruby's CSV module I believe I can only access the fields by index. I think the above code would be a bit more readable. Any ideas?

  • Marcos
    Marcos about 12 years
    So I loaded CSV file into an array with only allstocks << row inside the loop. How do I read one cell myrow[:company] where myrow[:ticker] == "ANAD"? There is only one record and ticker is my key field anyway.
  • scarver2
    scarver2 about 12 years
    Marcos - If the CSV has been converted into an array, you may have lost the the hashes (symbols). If this is the case, just reference the cell by the column number e.g. myrow[0].
  • Vicente Reig
    Vicente Reig almost 12 years
    Just discovered that much of these can be achieved already with the load and dump methods (Ruby 1.9/FasterCSV) github.com/JEG2/faster_csv/blob/master/test/tc_serialization‌​.rb
  • Vicente Reig
    Vicente Reig almost 12 years
    Something like this. It's a pretty cool feature actually! gist.github.com/3188109
  • PJP
    PJP about 8 years
    FasterCSV was incorporated into Ruby, I think it was in Ruby 1.9+.
  • jayqui
    jayqui over 5 years
    If you reeeally want dot syntax, you could require ostruct and then throw in a step of row_struct = OpenStruct.new(row.to_h), which would respond to row_struct.foo.
  • Cary Swoveland
    Cary Swoveland over 4 years
    Ruby has no built-in converter for symbols, so don't you need to first add one: CSV::Converters[:symbol] = ->(v) { v.to_sym }?