Chris Drappier Chris Drappier - 6 months ago 8
Ruby Question

what's the best way to convert a ruby hash to an array

I have a ruby hash that looks like this

{ "stuff_attributes" => {
"1" => {"foo" => "bar", "baz" => "quux"},
"2" => {"foo" => "bar", "baz" => "quux"}
}
}


and I want to turn it into a hash that looks like this

{ "stuff_attributes" => [
{ "foo" => "bar", "baz" => "quux"},
{ "foo" => "bar", "baz" => "quux"}
]
}


I also need to preserve the numerical order of the keys, and there is a variable number of keys. The above is super-simplified, but I've included a real example at the bottom. What's the best way to do this?

P.S

It also needs to be recursive

As far as the recursion goes, here's what we can assume:

1) the key that needs to be manipulated will match /_attributes$/
2) the hash will have many other keys that do not match /_attributes$/
3) the keys inside the hash will always be a number
4) an _attributes hash can be at any level of the hash under any other key

this hash is actually the params hash from a create action in the controller. This is a real example of what will need to be parsed with this routine.

{
"commit"=>"Save",
"tdsheet"=>{
"team_id"=>"43",
"title"=>"",
"performing_org_id"=>"10",
"tdsinitneed_attributes"=>{
"0"=>{
"title"=>"",
"need_date"=>"",
"description"=>"",
"expected_providing_organization_id"=>"41"
},
"1"=>{
"title"=>"",
"need_date"=>"",
"description"=>"",
"expected_providing_organization_id"=>"41"
}
},
"level_two_studycollection_id"=>"27",
"plan_attributes"=>{
"0"=>{
"start_date"=>"", "end_date"=>""
}
},
"dataitem_attributes"=>{
"0"=>{
"title"=>"",
"description"=>"",
"plan_attributes"=>{
"0"=>{
"start_date"=>"",
"end_date"=>""
}
}
},
"1"=>{
"title"=>"",
"description"=>"",
"plan_attributes"=>{
"0"=>{
"start_date"=>"",
"end_date"=>""
}
}
}
}
},
"action"=>"create",
"studycollection_level"=>"",
"controller"=>"tdsheets"
}

Answer

Note that this might be long to test if all keys are numbers before converting...

def array_from_hash(h)
  return h unless h.is_a? Hash

  all_numbers = h.keys.all? { |k| k.to_i.to_s == k }
  if all_numbers
    h.keys.sort_by{ |k| k.to_i }.map{ |i| array_from_hash(h[i]) }
  else
    h.each do |k, v|
      h[k] = array_from_hash(v)
    end
  end
end