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:
- The default behavior for
recursive_hash[:foo]
is the same asrecursive_hash
. - The default behavior for hashes created by
recursive_hash[:foo]
'sdefault_proc
will be the same asrecursive_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
How to Change Default Timezone For Active Record in Rails
Get the Name of the Currently Executing Method
Is There a "Do ... While" Loop in Ruby
Continuously Read from Stdout of External Process in Ruby
How to Count Duplicate Elements in a Ruby Array
How Does Object_Id Assignment Work
Need a Simple Explanation of the Inject Method
Sort an Array According to the Elements of Another Array
Is Ruby Pass by Reference or by Value
How to Redirect to a 404 in Rails
Ruby: String Comparison Issues
How to Get Source Code of a Method Dynamically and Also Which File Is This Method Locate In
How to Implement Enums in Ruby