Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to output a hash to a CSV line

Tags:

ruby

csv

I'd like to map a hash to a CSV line.

I have a couple of objects in a hash:

person1 = {'first_name' => 'John', 'second_name' => 'Doe', 'favorite_color' => 'blue', 'favorite_band' => 'Backstreet Boys'}
person2 = {'first_name' => 'Susan', 'favorite_color' => 'green', 'second_name' => 'Smith'}

I want to transform this into a CSV file with the keys as columns and the values for each row.

I can easily create the headers by creating a CSV::Row like this:

h = CSV::Row.new(all_keys_as_array,[],true)

I cannot rely on the order and more important, not all values are filled all the time.

But when I now try to add rows to the table via << and array, the mapping of the headers is ignored. It has to be in the right order.

To demonstrate this, I wrote this little script:

require 'csv'

person1 = {'first_name' => 'John', 'second_name' => 'Doe', 'favorite_color' => 'blue', 'favorite_band' => 'Backstreet Boys'}
person2 = {'first_name' => 'Susan', 'favorite_color' => 'green', 'second_name' => 'Smith'}

persons = [person1, person2]
all_keys_as_array = %w{first_name second_name favorite_color favorite_band}

h = CSV::Row.new(all_keys_as_array,[],true)
t = CSV::Table.new([h])

persons.each do |p|
  r = CSV::Row.new([],[],false)
  p.each do |k, v|
    r << {k => v}
  end
  t << r
end

puts t.to_csv

I would expect this output:

first_name,last_name,favorite_color,favorite_band
John,Doe,blue,Backstreet Boys
Susan,Smith,green,

Instead the values in the order as they appear. So the output is this:

first_name,second_name,favorite_color,favorite_band
John,Doe,blue,Backstreet Boys
Susan,green,Smith

The strangest part is, when I do a lookup via ['key'] I get the correct values:

puts "favorite_bands: #{t['favorite_band']}"
> favorite_bands: [nil, "Backstreet Boys", nil]

So is there any way to write to a CSV file as I expect?

like image 592
leifg Avatar asked Mar 01 '13 15:03

leifg


3 Answers

You can just iterate over the column names

persons.each do |person|
  r = CSV::Row.new([],[],false)
  all_keys_as_array.each do |key|
    r << person[key]
  end
  t << r
end
like image 156
Benjamin Udink ten Cate Avatar answered Sep 23 '22 23:09

Benjamin Udink ten Cate


The current suggestions both work, so thank you for this.

However I stumbled upon a more elegant solution by using CSV::Row#fields.

Then I can just convert the CSV row to the correct array before adding it to the table:

t << r.fields(*all_keys_as_array)
like image 28
leifg Avatar answered Sep 21 '22 23:09

leifg


You may be able to just use #to_csv and dispense with all the CSV::Row and CSV::Table stuff:

headers = %w{first_name second_name favorite_color favorite_band} # fyi if you have commas in here you'll get messed up

people = []
people << {'first_name' => 'John', 'second_name' => 'Doe', 'favorite_color' => 'blue', 'favorite_band' => 'Backstreet Boys'}
people << {'first_name' => 'Susan', 'favorite_color' => 'green', 'second_name' => 'Smith'}

require 'csv'
File.open('output.csv', 'w') do |f|
  f.puts headers.to_csv
  people.each do |person|
    f.puts headers.map { |h| person[h] }.to_csv
  end
end
like image 22
Seamus Abshere Avatar answered Sep 22 '22 23:09

Seamus Abshere