如何避免NoMethodError在嵌套哈希中缺少元素,而不重复nil检查?
我正在寻找一种避免在深层嵌套哈希中检查每个级别的nil
的好方法。 例如:
name = params[:company][:owner][:name] if params[:company] && params[:company][:owner] && params[:company][:owner][:name]
这需要三次检查,并且会产生非常难看的代码。 有办法解决这个问题吗?
Ruby 2.3.0在Hash
和Array
上引入了一个名为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
function性和清晰度之间的最佳折衷IMO是Raganwald和。 有了它,你会做:
params[:company].andand[:owner].andand[:name]
它类似于try
,但在这种情况下读取效果要好得多,因为你仍然像正常一样发送消息,但是在它之间有一个分隔符,要求注意你要特别处理nils的事实。
我不知道这是不是你想要的,但也许你可以这样做?
name = params[:company][:owner][:name] rescue nil
相当于用户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
如果是轨道,请使用
params.try(:[], :company).try(:[], :owner).try(:[], :name)
哦,等等,这甚至更加丑陋。 😉
如果你想进入monkeypatching,你可以做这样的事情
NilClass类 def [](任何东西) 零 结束 结束
然后调用params[:company][:owner][:name]
将产生nil,如果在任何时候嵌套哈希值之一为nil。
编辑:如果你想要一个更安全的路线,也提供干净的代码,你可以做类似的事情
哈希 def chain(* args) x = 0 current = self [args [x]] 而当前&& x代码如下所示:
params.chain(:company, :owner, :name)
您可能想要研究一种向ruby哈希添加自动生成的方法。 以下stackoverflow线程中提到了许多方法:
- Ruby Autovivification
- ruby hash autovivification(facets)
我会这样写:
name = params[:company] && params[:company][:owner] && params[:company][:owner][:name]
它没有那么干净? Io中的运算符 ,但Ruby没有。 @ThiagoSilveira的答案也很好,但速度会慢一些。
您是否能够避免使用多维哈希,并使用
params[[:company, :owner, :name]]
要么
params[[:company, :owner, :name]] if params.has_key?([:company, :owner, :name])
代替?
写一次丑陋,然后隐藏它
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
(即使这是一个非常古老的问题,也许这个答案对于像我这样没有想到“开始救援”控制结构表达的一些stackoverflow人会有用。)
我会尝试使用try catch语句(以ruby语言开始救援):
begin name = params[:company][:owner][:name] rescue #if it raises errors maybe: name = 'John Doe' end
做:
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']
您不需要访问原始哈希定义 – 您可以在使用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。
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
危险但有效:
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
“h_try”链遵循与“try”链类似的风格。
从Ruby 2.3.0开始:
你也可以使用&.
称为“安全导航操作员”为: params&.[](:company)&.[](:owner)&.[](:name)
。 这个非常安全。
使用dig
并不安全,因为如果params
为nil, params.dig(:company, :owner, :name)
将失败。
但是,您可以将两者合并为: params&.dig(:company, :owner, :name)
。
因此,以下任何一种都可以安全使用:
params&.[](:company)&.[](:owner)&.[](:name)
params&.dig(:company, :owner, :name)