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?
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
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)
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With