Ruby Style: How to Check Whether a Nested Hash Element Exists

Ruby Style: How to check whether a nested hash element exists

The most obvious way to do this is to simply check each step of the way:

has_children = slate[:person] && slate[:person][:children]

Use of .nil? is really only required when you use false as a placeholder value, and in practice this is rare. Generally you can simply test it exists.

Update: If you're using Ruby 2.3 or later there's a built-in dig method that does what's described in this answer.

If not, you can also define your own Hash "dig" method which can simplify this substantially:

class Hash
def dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end

This method will check each step of the way and avoid tripping up on calls to nil. For shallow structures the utility is somewhat limited, but for deeply nested structures I find it's invaluable:

has_children = slate.dig(:person, :children)

You might also make this more robust, for example, testing if the :children entry is actually populated:

children = slate.dig(:person, :children)
has_children = children && !children.empty?

Accessing elements of nested hashes in ruby

The way I usually do this these days is:

h = Hash.new { |h,k| h[k] = {} }

This will give you a hash that creates a new hash as the entry for a missing key, but returns nil for the second level of key:

h['foo'] -> {}
h['foo']['bar'] -> nil

You can nest this to add multiple layers that can be addressed this way:

h = Hash.new { |h, k| h[k] = Hash.new { |hh, kk| hh[kk] = {} } }

h['bar'] -> {}
h['tar']['zar'] -> {}
h['scar']['far']['mar'] -> nil

You can also chain indefinitely by using the default_proc method:

h = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }

h['bar'] -> {}
h['tar']['star']['par'] -> {}

The above code creates a hash whose default proc creates a new Hash with the same default proc. So, a hash created as a default value when a lookup for an unseen key occurs will have the same default behavior.

EDIT: More details

Ruby hashes allow you to control how default values are created when a lookup occurs for a new key. When specified, this behavior is encapsulated as a Proc object and is reachable via the default_proc and default_proc= methods. The default proc can also be specified by passing a block to Hash.new.

Let's break this code down a little. This is not idiomatic ruby, but it's easier to break it out into multiple lines:

1. recursive_hash = Hash.new do |h, k|
2. h[k] = Hash.new(&h.default_proc)
3. end

Line 1 declares a variable recursive_hash to be a new Hash and begins a block to be recursive_hash's default_proc. The block is passed two objects: h, which is the Hash instance the key lookup is being performed on, and k, the key being looked up.

Line 2 sets the default value in the hash to a new Hash instance. The default behavior for this hash is supplied by passing a Proc created from the default_proc of the hash the lookup is occurring in; ie, the default proc the block itself is defining.

Here's an example from an IRB session:

irb(main):011:0> recursive_hash = Hash.new do |h,k|
irb(main):012:1* h[k] = Hash.new(&h.default_proc)
irb(main):013:1> end
=> {}
irb(main):014:0> recursive_hash[:foo]
=> {}
irb(main):015:0> recursive_hash
=> {:foo=>{}}

When the hash at recursive_hash[:foo] was created, its default_proc was supplied by recursive_hash's default_proc. This has two effects:

  1. The default behavior for recursive_hash[:foo] is the same as recursive_hash.
  2. The default behavior for hashes created by recursive_hash[:foo]'s default_proc will be the same as recursive_hash.

So, continuing in IRB, we get the following:

irb(main):016:0> recursive_hash[:foo][:bar]
=> {}
irb(main):017:0> recursive_hash
=> {:foo=>{:bar=>{}}}
irb(main):018:0> recursive_hash[:foo][:bar][:zap]
=> {}
irb(main):019:0> recursive_hash
=> {:foo=>{:bar=>{:zap=>{}}}}

How to check if nested hash attributes are empty

Answer 1:

person_params[:addresses_attributes]['0']

Answer 2:

hash = person_params[:addresses_attributes]['0']
hash.empty?

How to get value of key or key presence from nested hash?

%w[a c e f].inject(a, &:fetch) # => 3
%w[a c e x].inject(a, &:fetch) # > Error key not found: "x"
%w[x].inject(a, &:fetch) # => Error key not found: "x"

Or, to avoid errors:

%w[a c e f].inject(a, &:fetch) rescue "Key not found" # => 3
%w[a c e x].inject(a, &:fetch) rescue "Key not found" # => Key not found
%w[x].inject(a, &:fetch) rescue "Key not found" # => Key not found

Nested hash defined?()

When using ActiveSupport (Rails) or Backports, you can use try:

@hash[:key1].try(:fetch, :key2)

You could even handle @hash being nil:

@hash.try(:fetch, :key1).try(:fetch, :key2)

If you want @hash to always return a hash for a missing key:

@hash = Hash.new { |h,k| h[k] = {} }
@hash[:foo] # => {}

You could also define this recursive:

def recursive_hash
Hash.new { |h,k| h[k] = recursive_hash }
end

@hash = recursive_hash
@hash[:foo][:bar][:blah] = 10
@hash # => {:foo => {:bar => {:blah => 10}}}

But to answer your question:

module HasNestedKey
Hash.send(:include, self)
def has_nested_key?(*args)
return false unless sub = self[args.shift]
return true if args.empty?
sub.respond_to?(:has_nested_key?) and sub.has_nested_key?(*args)
end
end

@hash.has_nested_key? :key1, :key2

Using array of strings to check for existence of nested property in hash

Are you on Ruby 2.3+? Try Hash#dig:

object_to_search_containing_property.dig(*property_selector) # => "fff"
object_to_search_not_containing_property.dig(*property_selector) # => nil

The only bugaboo is that the elements of your property selector array are strings, but you're building your nested object with symbol keys. Define your property selector array like this instead for the above code to work:

property_selector = [:component_styles, :styles, :background, :color]

Or, if you don't have control over how that array is defined, you can transform it on the fly:

object.dig(*property_selector.map(&:to_sym))

Here's a portable method:

def dig(object, *keys)
keys.each do |key|
break unless object.respond_to?(:[])

object = object[key]
end

object
end

dig(object_to_search_containing_property, *property_selector) # => "fff"
dig(object_to_search_not_containing_property, *property_selector) # => nil

The same caveat about the property selector array applies here as well.

How to correctly check for nested values in ruby

Assuming the date is always the value (taking the example of de), then you can use dig to reach every publishedDate, then Hash#values to for every hash/value and any?(&:present?) to check if any of the given values is not blank:

(payload.dig('fields', 'publishedDate') || {}).values.any?(&:present?)

In the eventual case that fields and/or publishedDate aren't present in the hash, it'll return nil, and you'll use {} to invoke values, resulting in an empty array with no present? values.

If the hash comes from a request params, you must know that Rails invokes with_indifferent_access on the object and your hash keys are accessible as symbols as well, so you could do:

(payload.dig(:fields, :publishedDate) || {}).values.any?(&:present?)

Find key/value pairs deep inside a hash containing an arbitrary number of nested hashes and arrays

Here's a simple recursive solution:

def nested_hash_value(obj,key)
if obj.respond_to?(:key?) && obj.key?(key)
obj[key]
elsif obj.respond_to?(:each)
r = nil
obj.find{ |*a| r=nested_hash_value(a.last,key) }
r
end
end

h = { foo:[1,2,[3,4],{a:{bar:42}}] }
p nested_hash_value(h,:bar)
#=> 42

Accessing values in nested hash securly (without tripping over nil)?

It is matter of coding style. With Ruby 2.0, you can also do this, which I may like better:

value = array[:first].to_h[:second].to_h[:four]

If you do not want to type to_h each time, you can define a method:

class Hash
def safe_fetch k; self[k].to_h end
end
value = array.safe_fetch(:first).safe_fetch(:second)[:four]


Related Topics



Leave a reply



Submit