Accessing Nested Hashes Directly

I've been having a problem for a while with Ruby where accessing a hash
within a hash will fail if the first hash is nil. As an example, Rails
accepts XML input to an action by passing it through REXML. On an XML
document like this:

  <body>
    <user>
      <name>
        <given>Tony</given>
        <family>Robbins</family>
      </name>
    </user>
  </body>

You will receive a hash that looks like this (within the params hash):

  "body" => { "user" => { "name" => { "given" => "Tony", "family" =>
"Robbins" } } }

Thus, you can access the elements like this:

  params["body"]["user"]["name"]["given"]

However, sometimes the "name" element might not come over the wire
(through no fault of Ruby's nor Rails' -- it's just a problem we have
to deal with in our system). In this case, Ruby will throw a
NoMethodError on nil.[] because we tried to access the value at "given"
on an element that does not exist. Furthermore, sometimes the "user"
element does not exist, either! This led me to code that looks like
this:

  if params["body"]["user"]
    if params["body"]["user"]["name"]
      first_name = params["body"]["user"]["name"]["given"]
    end
  end

That's not very elegant when you're doing it with many (15 - 30)
fields. I also tried doing it with exceptions:

  begin
    first_name = params["body"]["user"]["name"]["given"]
  rescue NoMethodError
  end

Also, not very elegant.

Finally, I wrote a method to work around this problem:

  def get_value(object, *path)
    new_object = object

    for item in path
      item = item.to_s

      if new_object
        if new_object[item]
          new_object = new_object[item]
        else
          new_object = nil
        end
      else
        break
      end
    end

    if new_object.kind_of?(String)
      return new_object.strip
    else
      return new_object
    end
  end

This method can be called like this:

  first_name = get_value(params, :body, :user, :name, :given)

It will traverse the hash and kick back a nil at the first problem it
finds instead of raising an exception, or will return the value if it
actually exists.

Here's my question, though: is this code efficient? Is there a better
way? Am I missing something fundamental in Ruby that would solve this
without the need for the new method?

I think you can rewrite that method like this:

  def get_value(hash, *path)
    path.inject(hash) { |obj, item| obj[item] || break }
  end

If you're up for some core-modifying mayhem, You could put it on Hash,
and extend with 'path' capabilities:

        class Hash
          alias :__fetch :
        
          def traverse(*path)
            path.inject(self) { |obj, item| obj.__fetch(item) || break }
          end
        
          def (*args)
            (args.length > 1) ? traverse(*args) : __fetch(*args)
          end
        end

This way works like this:
        
        h = {
          :name => {
            :first => 'Ross',
           },
           :contact => {
             :phone => {
               :office => '345345'
            }
          }
        }
        
        p h.traverse(:name, :first)
        # => "Ross"
        
        p h.traverse(:name, :middle)
        # => nil
        
        p h[:contact]
        # => {:phone=>{:office=>"345345"}}
        
        p h[:contact, :phone, :office]
        # => "345345"
        
        p h[:contact, :phone, :fax]
        # => nil
        
Of course, there may be better ways to solve the underlying problem...

···

On Wed, 2006-09-27 at 02:55 +0900, atraver@gmail.com wrote:

I've been having a problem for a while with Ruby where accessing a hash
within a hash will fail if the first hash is nil.
[...snipped...]
Finally, I wrote a method to work around this problem:

  def get_value(object, *path)
    new_object = object

    for item in path
      item = item.to_s

      if new_object
        if new_object[item]
          new_object = new_object[item]
        else
          new_object = nil
        end
      else
        break
      end
    end

    if new_object.kind_of?(String)
      return new_object.strip
    else
      return new_object
    end
  end

This method can be called like this:

  first_name = get_value(params, :body, :user, :name, :given)

It will traverse the hash and kick back a nil at the first problem it
finds instead of raising an exception, or will return the value if it
actually exists.

Here's my question, though: is this code efficient? Is there a better
way? Am I missing something fundamental in Ruby that would solve this
without the need for the new method?

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

atraver <at> gmail.com <atraver <at> gmail.com> writes:

However, sometimes the "name" element might not come over the wire
(through no fault of Ruby's nor Rails' -- it's just a problem we have
to deal with in our system). In this case, Ruby will throw a
NoMethodError on nil. because we tried to access the value at "given"
on an element that does not exist. Furthermore, sometimes the "user"
element does not exist, either! This led me to code that looks like
this:

  if params["body"]["user"]
    if params["body"]["user"]["name"]
      first_name = params["body"]["user"]["name"]["given"]
    end
  end

That's not very elegant when you're doing it with many (15 - 30)
fields.

I use something like the following, although it would also look unweildy when
chained together a lot

hsh.fetch(:key, {}) # returns either hsh[:key], or {} if !hsh.has_key?(:key)

so

hsh.fetch(:key1, {}).fetch(:key2, nil) # will work as expected, silently
returning nil on error

Gareth

What about:
first_name = params["body"]["user"]["name"]["given"] rescue nil

Hadley

···

On 9/26/06, atraver@gmail.com <atraver@gmail.com> wrote:

I've been having a problem for a while with Ruby where accessing a hash
within a hash will fail if the first hash is nil. As an example, Rails
accepts XML input to an action by passing it through REXML. On an XML
document like this:

  <body>
    <user>
      <name>
        <given>Tony</given>
        <family>Robbins</family>
      </name>
    </user>
  </body>

You will receive a hash that looks like this (within the params hash):

  "body" => { "user" => { "name" => { "given" => "Tony", "family" =>
"Robbins" } } }

Thus, you can access the elements like this:

  params["body"]["user"]["name"]["given"]

However, sometimes the "name" element might not come over the wire
(through no fault of Ruby's nor Rails' -- it's just a problem we have
to deal with in our system). In this case, Ruby will throw a
NoMethodError on nil. because we tried to access the value at "given"
on an element that does not exist. Furthermore, sometimes the "user"
element does not exist, either! This led me to code that looks like
this:

  if params["body"]["user"]
    if params["body"]["user"]["name"]
      first_name = params["body"]["user"]["name"]["given"]
    end
  end

That's not very elegant when you're doing it with many (15 - 30)
fields. I also tried doing it with exceptions:

  begin
    first_name = params["body"]["user"]["name"]["given"]
  rescue NoMethodError
  end

atraver@gmail.com wrote:

This method can be called like this:

  first_name = get_value(params, :body, :user, :name, :given)

It will traverse the hash and kick back a nil at the first problem it
finds instead of raising an exception, or will return the value if it
actually exists.

Here's my question, though: is this code efficient? Is there a better
way? Am I missing something fundamental in Ruby that would solve this
without the need for the new method?

I didn't really like the way that method is called, doesn't seem very "ruby" exactly. Came up with the following which is really dirty at the moment but I prefer how it's called. The method_missing in NilClass is kind of unforgivably dirty. Would like to see improvements I guess, but not sure there's really a way to do this nicely:

So, yeah, don't use this, but possibly be amused by it anyway:

require 'ostruct'
class NestStruct < OpenStruct
   def self.fromNestedHash(h)
     struct = NestStruct.new(h)
     struct.marshal_dump.keys.each do |key|
       if struct.send(key).is_a? Hash
         struct.send(key.to_s + '=', fromNestedHash(struct.send(key)))
       end
     end
     return struct
   end

   def method_missing(m, *args)
     super(m, args)
   rescue NoMethodError
     nil
   end
end

class NilClass
   def method_missing(m, *args)
     nil
   end
end

h = {
   'name' => {
     'first' => 'Ross'
   },
   'contact' => {
          'phone' => {
       'office' => '345345'
     }
   }
}

s = NestStruct.fromNestedHash(h)
puts s.contact.phone # => #<NestStruct office="345345">
puts s.contact.phone.office # => 345345
puts s.contact.phone.home # => nil
puts s.foo.bar # => nil

Posted by Adam Traver
on 26.09.2006 19:57

I've been having a problem for a while with Ruby where accessing a hash
within a hash will fail if the first hash is nil.

...

You will receive a hash that looks like this (within the params hash):

"body" => { "user" => { "name" => { "given" => "Tony", "family" => "Robbins" } } }

Thus, you can access the elements like this:

params["body"]["user"]["name"]["given"]

...

Here's my question, though: is this code efficient? Is there a better
way? Am I missing something fundamental in Ruby that would solve this
without the need for the new method?

Yet another (though indirect) approach could be to first extract all
keys from the nested hash!

···

--
Posted via http://www.ruby-forum.com/\.

Thanks for all the responses.

Here is the method I ended up with after looking over everything, just
in case this topic is searched for in the future:

  def get_value(object, *path)
    for item in path
      break unless object
      object = object ? object[item.to_s] : nil
    end

    return object.kind_of?(String) ? object.strip : object
  end

This might be a little more verbose than some of the solutions (also,
Ross, your "inject" solution didn't seem to work out although I'm sure
it would with some tweaking). However, it ends up doing exactly what I
need it to do: get the value or return nil, in a quick-and-dirty way.

-Adam

Hmm, out of interest, what didn't work out about it? Maybe I
misunderstand what you're doing, but (if you ignore the Hash-extending
silliness) it seems to work the same.

require 'test/unit'

def get_value(object, *path)
  for item in path
    break unless object
    object = object ? object[item.to_s] : nil
  end

  return object.kind_of?(String) ? object.strip : object
end

def my_get_value(hsh, *path)
  path.inject(hsh) { |obj, item| obj[item.to_s] || break }
end

class TestGetValue < Test::Unit::TestCase
  def setup
    @h = {
      'name' => {
        'first' => 'Ross'
       },
       'contact' => {
         'phone' => {
           'office' => '345345'
          }
        }
      }
  end

  def test_it
    assert_equal get_value(@h, :name, :first),
                 my_get_value(@h, :name, :first)
    assert_equal get_value(@h, :name, :middle),
                 my_get_value(@h, :name, :middle)
    assert_equal get_value(@h, :contact),
                 my_get_value(@h, :contact)
    assert_equal get_value(@h, :contact, :phone, :office),
                 my_get_value(@h, :contact, :phone, :office)
  end
end

# -> 1 tests, 4 assertions, 0 failures, 0 errors

The only thing I changed here is adding to_s on the item in my version,
so I could use the same hash to test both while keeping symbol keys.

···

On Thu, 2006-09-28 at 02:05 +0900, atraver@gmail.com wrote:

Thanks for all the responses.

Here is the method I ended up with after looking over everything, just
in case this topic is searched for in the future:

  def get_value(object, *path)
    for item in path
      break unless object
      object = object ? object[item.to_s] : nil
    end

    return object.kind_of?(String) ? object.strip : object
  end

This might be a little more verbose than some of the solutions (also,
Ross, your "inject" solution didn't seem to work out although I'm sure
it would with some tweaking). However, it ends up doing exactly what I
need it to do: get the value or return nil, in a quick-and-dirty way.

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk