Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Smartly converting array of hashes to CSV in ruby

Tags:

arrays

ruby

csv

I need to convert an array of hashes in a CSV file. The various methods I found involve inserting in the array the hash values:

class Array
  def to_csv(csv_filename="hash.csv")
    require 'csv'
    CSV.open(csv_filename, "wb") do |csv|
      csv << first.keys # adds the attributes name on the first line
      self.each do |hash|
        csv << hash.values
      end
    end
  end
end

Unfortunately this method requires that each element in the array is complete, for example when I have this array it won't even return a valid csv:

myarray = [
  {foo: 1, bar: 2, baz: 3},
  {bar: 2, baz: 3},
  {foo: 2, bar: 4, baz: 9, zab: 44}
]

I'm looking for a way to create a csv that finds all the possible headers, and assigns the values in correct order, adding empty spaces where needed.

like image 413
TopperH Avatar asked Aug 21 '16 16:08

TopperH


2 Answers

What about:

class Array
  def to_csv(csv_filename="hash.csv")
    require 'csv'
    # Get all unique keys into an array:
    keys = self.flat_map(&:keys).uniq
    CSV.open(csv_filename, "wb") do |csv|
      csv << keys
      self.each do |hash|
        # fetch values at keys location, inserting null if not found.
        csv << hash.values_at(*keys)
      end
    end
  end
end
like image 75
hirolau Avatar answered Sep 20 '22 14:09

hirolau


I would do it in this way. It's quite brute force because it needs to find all the headers that exist and also needs to fill the empty elements..

class Array
  def to_csv(csv_filename='test.csv')
    require 'csv'

    headers = []
    self.each {|hash| headers += hash.keys}
    headers = headers.uniq

    rows = []
    self.each do |hash|
      arr_row = []
      headers.each {|header| arr_row.push(hash.key?(header) ? hash[header] : nil)}
      csv_row = CSV::Row.new(headers, arr_row)
      rows.push(csv_row)
    end
    csv_table = CSV::Table.new(rows)
    File.open(csv_filename, 'w'){|file| file << csv_table.to_s}
  end
end

Take a look at CSV::Row and CSV::Table classes. I find them handy.

like image 31
user3458580 Avatar answered Sep 19 '22 14:09

user3458580