How do I clean a data with hash and array mixed?

I have a hash data like this:

{
  "current_condition" => [
    {
      "cloudcover"       => "100",
      "humidity"         => "100",
      "observation_time" => "05:44 AM",
      "precipMM"         => "0.0",
      "pressure"         => "1015",
      "temp_C"           => "14",
      "temp_F"           => "57",
      "visibility"       => "13",
      "weatherCode"      => "122",
      "weatherDesc"      => [
        {
          "value" => "Overcast"
        }
      ],
      "weatherIconUrl" => [
        {
          "value" => "http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0004_black_low_cloud.png"
        }
      ],
      "winddir16Point" => "NNW",
      "winddirDegree"  => "340",
      "windspeedKmph"  => "15",
      "windspeedMiles" => "9"
    }
  ], 
  "request" => [
    {
      "query" => "94127",
      "type"  => "Zipcode"
    }
  ],
  "weather" => [
    {
      "date"        => "2012-09-09",
      "precipMM"    => "0.0",
      "tempMaxC"    => "21",
      "tempMaxF"    => "69",
      "tempMinC"    => "12",
      "tempMinF"    => "53",
      "weatherCode" => "113",
      "weatherDesc" => [
        {
          "value" => "Sunny"
        }
      ],
      "weatherIconUrl" => [
        {
          "value" => "http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0001_sunny.png"
        }
      ],
      "winddir16Point" => "W",
      "winddirDegree"  => "279",
      "winddirection"  => "W",
      "windspeedKmph"  => "23",
      "windspeedMiles" => "14"
    },
    {
      "date"        => "2012-09-10",
      "precipMM"    => "0.1",
      "tempMaxC"    => "20",
      "tempMaxF"    => "68",
      "tempMinC"    => "12",
      "tempMinF"    => "53",
      "weatherCode" => "119",
      "weatherDesc" => [
        {
          "value" => "Cloudy"
        }
      ],
      "weatherIconUrl" => [
        {
          "value" => "http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0003_white_cloud.png"
        }
      ],
      "winddir16Point" => "WSW",
      "winddirDegree"  => "252",
      "winddirection"  => "WSW",
      "windspeedKmph"  => "17",
      "windspeedMiles" => "11"
    }
  ]
}

Some of the hash values are just strings, some of them are arrays with only one element like this:

"weatherDesc"=>[{"value"=>"Cloudy"}]

I want to make all the elements like this in the hash to be like:

"weatherDesc"=>{"value"=>"Cloudy"}

Is there an easy Ruby method, or a single loop that can do this? Or do I need to loop through each key-val pair to flatten this?

--update -9-11-2012

Thanks for those who discussed and helped me out. Here's an update, I just found that there's actually one hash value has 2 objects in the array, I modified the @iioiooioo 's code in this line

hash[k] = v.first if v.is_a?( Array ) && v.count == 1

--more update on this, that above doesn't work correctly since there are arrays in the array who doesn't get cleaned since the array with 2 elements is not processed, which would end the recursion on it. I end up doing this, which is not pretty but...

def arr_to_hash(a)
  hash = {}
  for i in 0..a.length-1
      hash[i.to_s] = a[i]
  end
  hash
end

def clean_it( hash )
  hash.each do |k,v|
  hash[k] = arr_to_hash v if v.is_a?( Array ) && v.count > 1
  hash[k] = v.first if v.is_a?( Array ) && v.count == 1
  clean_it( hash[k] ) if hash[k].is_a?( Hash )
  end
end

Answers


I do not believe a simple loop will suffice, I think you need to use recursion, like so:

a = {"current_condition"=> [{"cloudcover"=>"100", "humidity"=>"100", "observation_time"=>"05:44 AM", "precipMM"=>"0.0", "pressure"=>"1015", "temp_C"=>"14", "temp_F"=>"57", "visibility"=>"13", "weatherCode"=>"122", "weatherDesc"=>[{"value"=>"Overcast"}], "weatherIconUrl"=>[{"value"=>"http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0004_black_low_cloud.png"}], "winddir16Point"=>"NNW", "winddirDegree"=>"340", "windspeedKmph"=>"15", "windspeedMiles"=>"9"}], "request"=>[{"query"=>"94127", "type"=>"Zipcode"}], "weather"=>[{"date"=>"2012-09-09", "precipMM"=>"0.0", "tempMaxC"=>"21", "tempMaxF"=>"69", "tempMinC"=>"12", "tempMinF"=>"53", "weatherCode"=>"113", "weatherDesc"=>[{"value"=>"Sunny"}], "weatherIconUrl"=>[{"value"=>"http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0001_sunny.png"}], "winddir16Point"=>"W", "winddirDegree"=>"279", "winddirection"=>"W", "windspeedKmph"=>"23", "windspeedMiles"=>"14"}, {"date"=>"2012-09-10", "precipMM"=>"0.1", "tempMaxC"=>"20", "tempMaxF"=>"68", "tempMinC"=>"12", "tempMinF"=>"53", "weatherCode"=>"119", "weatherDesc"=>[{"value"=>"Cloudy"}], "weatherIconUrl"=>[{"value"=>"http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0003_white_cloud.png"}], "winddir16Point"=>"WSW", "winddirDegree"=>"252", "winddirection"=>"WSW", "windspeedKmph"=>"17", "windspeedMiles"=>"11"}]}


def clean_it( hash )

 hash.each do |k,v|
  hash[k] = v.first if v.is_a?( Array )
  clean_it( hash[k] ) if hash[k].is_a?( Hash )
 end

end

clean_it( a )

p a

Outputs:

{"current_condition"=>{"cloudcover"=>"100", "humidity"=>"100", "observation_time"=>"05:44 AM", "precipMM"=>"0.0", "pressure"=>"1015", "temp_C"=>"14", "temp_F"=>"57", "visibility"=>"13", "weatherCode"=
>"122", "weatherDesc"=>{"value"=>"Overcast"}, "weatherIconUrl"=>{"value"=>"http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0004_black_low_cloud.png"}, "winddir16Point"=>"NNW", "wind
dirDegree"=>"340", "windspeedKmph"=>"15", "windspeedMiles"=>"9"}, "request"=>{"query"=>"94127", "type"=>"Zipcode"}, "weather"=>{"date"=>"2012-09-09", "precipMM"=>"0.0", "tempMaxC"=>"21", "tempMaxF"=>"
69", "tempMinC"=>"12", "tempMinF"=>"53", "weatherCode"=>"113", "weatherDesc"=>{"value"=>"Sunny"}, "weatherIconUrl"=>{"value"=>"http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0001_s
unny.png"}, "winddir16Point"=>"W", "winddirDegree"=>"279", "winddirection"=>"W", "windspeedKmph"=>"23", "windspeedMiles"=>"14"}}

I agree with @iioiooioo, you should definitely use recursion. I think this should do what you want:

def collapse(obj)
  return obj unless obj.respond_to? :each
  if obj.is_a? Array
    ary = obj.map{ |elem| collapse elem }
    ary.first if ary.length == 1
  elsif obj.is_a? Hash
    obj.each { |key, val| obj[key] = collapse val }
    obj
  end
end

Need Your Help

Google Java Style: Import names v.s. Import statements?

java coding-style

I'm looking through Google's Java Style guide and in section 3.3.3 Import Ordering and Spacing it says:

After publishing, my Umbraco admin panel did not show in IE9. But it appear in FF and Chrome browsers

asp.net website umbraco publishing-site

After publishing, my Umbraco admin panel did not show in IE9. But it appear in FF and Chrome browsers.

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.