31

How do you convert an array of hashes to a .csv file?

I have tried

    CSV.open("data.csv", "wb") do |csv|
      @data.to_csv
    end

but it is blank

  • You have a variable (`csv`) which is set, but never referenced. This often is (as in your case) an indicator for an error. – user1934428 Mar 01 '21 at 09:35

5 Answers5

59

Try this:

CSV.open("data.csv", "wb") do |csv|
  @data.each do |hash|
    csv << hash.values
  end
end

If you want the first line of the CSV to contain the keys of the hash (a header row), simply do:

CSV.open("data.csv", "wb") do |csv|
  csv << @data.first.keys # adds the attributes name on the first line
  @data.each do |hash|
    csv << hash.values
  end
end

Please read the comment of @cgenco below: He wrote a monkey patch for the Array class.

MrYoshiji
  • 54,334
  • 13
  • 124
  • 117
  • this works however i want to have the keys as well and this lists only the values –  Jun 26 '13 at 16:51
  • How do you want it? Have you seen my second part of code? Can you post an example of the data you have in your arrays of Hashes and how you want it to be in the csv please? @SamanthaKlonaris – MrYoshiji Jun 26 '13 at 16:54
  • 1
    Also, you can try directly `csv << @data.to_csv` inside the open do block. – MrYoshiji Jun 26 '13 at 16:55
  • When I add that line I get NoMethodError for "csv << @data.first.keys.to_s" –  Jun 26 '13 at 16:56
  • 5
    I wrote a monkeypatch based on this code so you can just call `array.to_csv(csv_filename)`: https://gist.github.com/christiangenco/8acebde2025bf0891987 – cgenco Jun 06 '14 at 04:37
  • Why `"wb"` shouldn't be `"wt"`? – fguillen Aug 16 '18 at 12:20
  • This works fine if you're sure **all rows have the values in the same order** and **have all the same keys**. For a safer solution, see my answer. – iGEL Mar 03 '21 at 13:42
7

CSV is smart enough to deal with the non-uniform hashes for you. See the code for CSV::Writer#<<

So, this works, and is a bit simpler than the above examples:

CSV.open("data.csv", "wb", {headers: @data.first.keys} ) do |csv|
  @data.each do |hash|
    csv << hash
  end
end
Kazuki
  • 1,462
  • 14
  • 34
nbrustein
  • 727
  • 5
  • 16
7

If the keys are not the same in all rows, the current answers fail. This is the safest approach:

data = [{a: 1, b: 2}, {b: 3, c: 4}]

CSV.open("data.csv", "w") { |csv|
  headers = data.flat_map(&:keys).uniq
  csv << headers
  data.each { |row|
    csv << row.values_at(*headers)
  }
}

All keys will be present in the CSV, even if they don't appear in the first row:

a b c
1 2
3 4
iGEL
  • 16,540
  • 11
  • 60
  • 74
5

If the hashes aren't uniform then you will end up with data in the wrong columns. You should use values_at instead:

CSV.open("data.csv", "wb") do |csv|
  keys = @data.first.keys
  csv << keys
  @data.each do |hash|
    csv << hash.values_at(*keys)
  end
end
Ariel Cabib
  • 2,042
  • 22
  • 14
2

None of the other answers worked for me for one reason or another, so I'll throw in my contribution as well. Here's what worked for me for my array of hashes with ruby 2.7:

headers = data.map(&:keys).flatten.uniq
CSV.open("data.csv", "wb", {headers: headers} ) do |csv|
  csv << headers

  data.each do |hash|
    csv << hash
  end
end
John Bachir
  • 22,495
  • 29
  • 154
  • 227