yesyoukenn yesyoukenn -3 years ago 104
Ruby Question

Dynamically create hash from array of arrays

I want to dynamically create a Hash without overwriting keys from an array of arrays. Each array has a string that contains the nested key that should be created. However, I am running into the issue where I am overwriting keys and thus only the last key is there

data = {}

values = [
["income:concessions", 0, "noi", "722300", "purpose", "refinancing"],
["fees:fee-one", "0" ,"income:gross-income", "900000", "expenses:admin", "7500"],
["fees:fee-two", "0", "address:zip", "10019", "expenses:other", "0"]
]


What it should look like:

{
"income" => {
"concessions" => 0,
"gross-income" => "900000"
},
"expenses" => {
"admin" => "7500",
"other" => "0"
}
"noi" => "722300",
"purpose" => "refinancing",
"fees" => {
"fee-one" => 0,
"fee-two" => 0
},

"address" => {
"zip" => "10019"
}
}


This is the code that I currently, have how can I avoid overwriting keys when I merge?

values.each do |row|
Hash[*row].each do |key, value|
keys = key.split(':')

if !data.dig(*keys)
hh = keys.reverse.inject(value) { |a, n| { n => a } }
a = data.merge!(hh)
end

end
end

Answer Source

The code you've provided can be modified to merge hashes on conflict instead of overwriting:

values.each do |row|
  Hash[*row].each do |key, value|
    keys = key.split(':')

    if !data.dig(*keys)
      hh = keys.reverse.inject(value) { |a, n| { n => a } }
      data.merge!(hh) { |_, old, new| old.merge(new) }
    end
  end
end

But this code only works for the two levels of nesting.

By the way, I noted ruby-on-rails tag on the question. There's deep_merge method that can fix the problem:

values.each do |row|
  Hash[*row].each do |key, value|
    keys = key.split(':')

    if !data.dig(*keys)
      hh = keys.reverse.inject(value) { |a, n| { n => a } }
      data.deep_merge!(hh)
    end
  end
end
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download