可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
This question already has answers here:
Closed 4 years ago.
What's the most concise way to determine if @hash[:key1][:key2]
is defined, that does not throw an error if @hash
or @hash[:key1]
are nil?
defined?(@hash[:key1][:key2])
returns True if @hash[:key1]
exists (it does not determine whether :key2
is defined)
回答1:
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
回答2:
Perhaps I am missing something, but if all you care about is concise...why not:
@hash && @hash[:key1] && @hash[:key1][:key2]
or if you want to save a few characters
@hash && (h = @hash[:key1]) && h[:key2]
if any part of this fails, it returns nil
otherwise it returns the value associated with :key2
or true
.
The reason the defined?
returns true even if :key2
is not there is because it just checks whether the object you are referencing exists, which in that case is the method []
which is an alias for the method fetch
which does exist on the hash @hash[:key1]
but if that were to return nil, there is no fetch method on nil
and it would return nil
. That being said, if you had to go n
deep into an embedded hash, at some point it would become more efficient to call:
defined?(@hash[:key1][:key2][:key3]) && @hash[:key1][:key2][:key3]
回答3:
Using Hash#fetch
You can use the Hash#fetch method with a default of {}
so that it is safe to call has_key?
even if the first level key doesn't exist. e.g.
!hash.nil? && hash.fetch(key1, {}).has_key?(key2)
Alternative
Alternatively you can use the conditional operator e.g.
!hash.nil? && (hash.has_key?(key1) ? hash[key1].has_key?(key2) : false)
i.e. if hash
doesn't have key key1
then just return false
without looking for the second level key. If it does have key1
then return the result of checking key1's
value for key2
.
Also, if you want to check that hash[key1]'s
value has a has_key?
method before calling it:
!hash.nil? && (hash.has_key?(key1) ? hash[key1].respond_to?(:has_key?) &&
hash[key1].has_key?(key2) : false)
回答4:
@hash[:key1].has_key? :key2
回答5:
If you don't care about distinguishing nonexistent @hash[:key1][:key2]
(at any of 3 levels) from @hash[:key1][:key2] == nil
, this is quite clean and works for any depth:
[:key1,:key2].inject(hash){|h,k| h && h[k]}
If you want nil
to be treated as existing, use this instead:
(hash[:key1].has_key?(:key2) rescue false)
回答6:
Another option, one that I just discovered, is to extend Hash with a seek
method. Technique comes from Corey O'Daniel.
Stick this in an initializer:
class Hash
def seek(*_keys_)
last_level = self
sought_value = nil
_keys_.each_with_index do |_key_, _idx_|
if last_level.is_a?(Hash) && last_level.has_key?(_key_)
if _idx_ + 1 == _keys_.length
sought_value = last_level[_key_]
else
last_level = last_level[_key_]
end
else
break
end
end
sought_value
end
end
Then just call:
@key_i_need = @hash.seek :one, :two, :three
You'll get the value, or nil if it doesn't exist.