我正在寻找一种避免在深层嵌套哈希中检查每个级别nil
的好方法。例如:
name = params[:company][:owner][:name] if params[:company] && params[:company][:owner] && params[:company][:owner][:name]
这需要三次检查,并且会产生非常难看的代码。有办法解决这个问题吗?
答案 0 :(得分:28)
Ruby 2.3.0在Hash
和Array
上引入了a new method called dig
,完全解决了这个问题。
name = params.dig(:company, :owner, :name)
如果在任何级别缺少密钥,则返回nil
。
如果您使用的是早于2.3版本的Ruby,您可以使用ruby_dig gem或自行实施:
module RubyDig
def dig(key, *rest)
if value = (self[key] rescue nil)
if rest.empty?
value
elsif value.respond_to?(:dig)
value.dig(*rest)
end
end
end
end
if RUBY_VERSION < '2.3'
Array.send(:include, RubyDig)
Hash.send(:include, RubyDig)
end
答案 1 :(得分:11)
功能性和清晰度之间的最佳折衷IMO是Raganwald的andand
。有了它,你会这样做:
params[:company].andand[:owner].andand[:name]
它类似于try
,但在这种情况下阅读效果要好得多,因为你仍然像正常一样发送消息,但是在它之间有一个分隔符,要求注意你要特别处理nils的事实。 / p>
答案 2 :(得分:6)
我不知道这是不是你想要的,但也许你可以这样做?
name = params[:company][:owner][:name] rescue nil
答案 3 :(得分:4)
您可能想要查看将auto-vivification添加到ruby哈希的方法之一。以下stackoverflow线程中提到了许多方法:
答案 4 :(得分:4)
相当于用户mpd
建议的第二个解决方案,只有更惯用的Ruby:
class Hash
def deep_fetch *path
path.inject(self){|acc, e| acc[e] if acc}
end
end
hash = {a: {b: {c: 3, d: 4}}}
p hash.deep_fetch :a, :b, :c
#=> 3
p hash.deep_fetch :a, :b
#=> {:c=>3, :d=>4}
p hash.deep_fetch :a, :b, :e
#=> nil
p hash.deep_fetch :a, :b, :e, :f
#=> nil
答案 5 :(得分:3)
如果你想进入monkeypatching,你可以做这样的事情
class NilClass
def [](anything)
nil
end
end
如果在任何一个嵌套哈希值为零的情况下,对params[:company][:owner][:name]
的调用将产生nil。
编辑:
如果你想要一个更安全的路线,也提供干净的代码,你可以做类似的事情
class Hash
def chain(*args)
x = 0
current = self[args[x]]
while current && x < args.size - 1
x += 1
current = current[args[x]]
end
current
end
end
代码如下所示:params.chain(:company, :owner, :name)
答案 6 :(得分:3)
如果是轨道,请使用
params.try(:[], :company).try(:[], :owner).try(:[], :name)
哦,等等,这甚至更加丑陋。 ; - )
答案 7 :(得分:2)
我会把它写成:
name = params[:company] && params[:company][:owner] && params[:company][:owner][:name]
它不像? operator in Io那么干净,但Ruby没有那个。 @ThiagoSilveira的答案也很好,但速度会慢一些。
答案 8 :(得分:1)
您是否可以避免使用多维哈希,并使用
params[[:company, :owner, :name]]
或
params[[:company, :owner, :name]] if params.has_key?([:company, :owner, :name])
代替?
答案 9 :(得分:1)
写一次丑陋,然后隐藏它
def check_all_present(hash, keys)
current_hash = hash
keys.each do |key|
return false unless current_hash[key]
current_hash = current_hash[key]
end
true
end
答案 10 :(得分:1)
执行:
params.fetch('company', {}).fetch('owner', {})['name']
同样在每个步骤中,您可以使用NilClass
中内置的适当方法来逃避nil
,如果它是数组,字符串或数字。只需将to_hash
添加到此列表的清单中即可使用它。
class NilClass; def to_hash; {} end end
params['company'].to_hash['owner'].to_hash['name']
答案 11 :(得分:1)
(即使这是一个非常古老的问题,也许这个答案对于像我这样没有想到“开始救援”控制结构表达的一些stackoverflow人会有用。)
我会尝试使用try catch语句(以ruby语言开始救援):
begin
name = params[:company][:owner][:name]
rescue
#if it raises errors maybe:
name = 'John Doe'
end
答案 12 :(得分:0)
仅需提供dig
的单张图片,请尝试我编写的KeyDial宝石。本质上,这是dig
的包装器,但重要的区别在于它永远不会出错。
dig
编辑,则 dig
仍然会抛出错误。
hash = {a: {b: {c: true}, d: 5}}
hash.dig(:a, :d, :c) #=> TypeError: Integer does not have #dig method
在这种情况下,dig
对您没有帮助,您不仅需要返回hash[:a][:d].nil? &&
,而且还要返回hash[:a][:d].is_a?(Hash)
检查。 KeyDial可以让您执行此操作,而不会出现此类检查或错误:
hash.call(:a, :d, :c) #=> nil
hash.call(:a, :b, :c) #=> true
答案 13 :(得分:0)
TLDR; params&.dig(:company, :owner, :name)
从Ruby 2.3.0开始:
您还可以将名为“安全导航操作员”的&.
用作:params&.[](:company)&.[](:owner)&.[](:name)
。这个是非常安全的。
在dig
上使用params
实际上并不安全,因为如果params.dig
为零,params
将会失败。
但是,您可以将两者合并为:params&.dig(:company, :owner, :name)
。
因此,以下任何一种都可以安全使用:
params&.[](:company)&.[](:owner)&.[](:name)
params&.dig(:company, :owner, :name)
答案 14 :(得分:0)
危险但有效:
class Object
def h_try(key)
self[key] if self.respond_to?('[]')
end
end
我们可以做新事
user = {
:first_name => 'My First Name',
:last_name => 'my Last Name',
:details => {
:age => 3,
:birthday => 'June 1, 2017'
}
}
user.h_try(:first_name) # 'My First Name'
user.h_try(:something) # nil
user.h_try(:details).h_try(:age) # 3
user.h_try(:details).h_try(:nothing).h_try(:doesnt_exist) #nil
&#34; h_try&#34;链条遵循类似于&#34;尝试&#34;链
答案 15 :(得分:0)
require 'xkeys' # on rubygems.org
params.extend XKeys::Hash # No problem that we got params from somebody else!
name = params[:company, :owner, :name] # or maybe...
name = params[:company, :owner, :name, :else => 'Unknown']
# Note: never any side effects for looking
# But you can assign too...
params[:company, :reviewed] = true
答案 16 :(得分:0)
您不需要访问原始哈希定义 - 您可以在使用h.instance_eval获取后立即覆盖[]方法,例如
h = {1 => 'one'}
h.instance_eval %q{
alias :brackets :[]
def [] key
if self.has_key? key
return self.brackets(key)
else
h = Hash.new
h.default = {}
return h
end
end
}
但是这不会帮助你使用你拥有的代码,因为你依靠一个不合理的值来返回一个假值(例如,nil),如果你做了任何“正常”自动生成的东西在上面你将得到一个空的哈希表示未完成的值,其值为“true”。
你可以这样做 - 它只检查定义的值并返回它们。你不能用这种方式设置它们,因为我们无法知道调用是否在作业的LHS上。
module AVHash
def deep(*args)
first = args.shift
if args.size == 0
return self[first]
else
if self.has_key? first and self[first].is_a? Hash
self[first].send(:extend, AVHash)
return self[first].deep(*args)
else
return nil
end
end
end
end
h = {1=>2, 3=>{4=>5, 6=>{7=>8}}}
h.send(:extend, AVHash)
h.deep(0) #=> nil
h.deep(1) #=> 2
h.deep(3) #=> {4=>5, 6=>{7=>8}}
h.deep(3,4) #=> 5
h.deep(3,10) #=> nil
h.deep(3,6,7) #=> 8
但是,您只能用它来检查值 - 而不是分配它们。所以它并不是真正的自动生存,因为我们都知道并喜欢Perl。