Converting a ruby structure to a hash

128 views Asked by At

I've got a bit of a headache here - I'm pretty new to Ruby...

I've got a structure like so returned from Savon...

{
  :item => [{
              :key =>  "result",
              :value=> "success"
            },
            {
              :key =>  "data",
              :value=> {
                  :item => [{ :key => "displayName", 
                              :value => "Matt" 
                            },
                            {
                              :key => "messages",
                              :value => { 
                                  :items => [{....}]
                            }
                       ]
               }
       }]
}

And I need to get it to be like...

{
  :result => success
  :data => [{
              :displayName => "Matt"
           },
           {
              :messages => [{
                              :messageName => "Test Message"
                           }]
           }]
}

So I can easily manipulate and navigate the data structure I've tried a few methods including just passing the value of :item ...

def convertData(data)
  result = {}
  if(data!=nil)
    data.each do |item|
      key = item[:key] 
      value = item[:value] 
      if(value.instance_of?(Hash))
        result[key] = convertData(value[:item])
      else
        result[key] = value
      end  
    end
  end
  return result
end

But this just gives me a tonne of Type errors for symbol to integer conversion (I assume that's the array index playing up). Any help much appreciated.

2

There are 2 answers

1
tadman On BEST ANSWER

When trying to tackle problem like this in Ruby, it's useful to think of it in terms of transformations on your data. The Ruby Enumerable library is full of methods that help you manipulate regular data structures like Array and Hash.

A Ruby solution to this problem looks like:

def desavon(data)
  case (data)
  when Hash
    if (data[:item])
      data[:item].collect do |item|
        { item[:key] => desavon(item[:value]) }
      end
    else
      # raise error?
    end
  else
    data
  end
end

Here's some sample input data and sample output:

input = {
  item: [
    {
      key: "result",
      value: "success"
    },
    {
      key:  "data",
      value: {
        item: [
          {
            key: "displayName", 
            value: "Matt" 
          },
          {
            key: "messages",
            value: { 
              item: [
                {
                  key: 'messageName',
                  value: 'Test Message'
                }
              ]
            }
          }
        ]
      }
    }
  ]
}

desavon(input)
# => [{"result"=>"success"}, {"data"=>[{"displayName"=>"Matt"}, {"messages"=>[{"messageName"=>"Test Message"}]}]}]

I think this version looks better, output-wise, but that's a call you'll have to make:

def desavon(data)
  case (data)
  when Hash
    if (data[:item])
      Hash[
        data[:item].collect do |item|
          [ item[:key], desavon(item[:value]) ]
        end
      ]
    else
      # raise error?
    end
  else
    data
  end
end

desavon(input)
# => {"result"=>"success", "data"=>{"displayName"=>"Matt", "messages"=>{"messageName"=>"Test Message"}}}

Note that the case statement here is really the key, it allows you to quickly differentiate between different types of data you're converting, and the Hash[] method converts key-value pairs into the Hash structure you're looking for.

This is similar to your attempt, but just passes through content it doesn't recognize as-is.

1
Steffen Roller On

Savon provides a to_hash method on the result. What I usually do is:

...

response = @client.request :wsdl, :conversion_rate do
  soap.body = {
    "FromCurrency" => from_curr,
    "ToCurrency" => to_curr
  }
end    
response.to_hash[:conversion_rate_response][:conversion_rate_result];

... full script here https://gist.github.com/sroller/3d04842ab763f52b6623