我正在寻找一种避免nil
在深度嵌套的哈希中检查每个级别的好方法。例如:
name = params[:company][:owner][:name] if params[:company] && params[:company][:owner] && params[:company][:owner][:name]
这需要三项检查,并且代码非常难看。有什么办法可以解决这个问题?
我正在寻找一种避免nil
在深度嵌套的哈希中检查每个级别的好方法。例如:
name = params[:company][:owner][:name] if params[:company] && params[:company][:owner] && params[:company][:owner][:name]
这需要三项检查,并且代码非常难看。有什么办法可以解决这个问题?
Ruby 2.3.0 引入了一种新方法,在这两者上都调用dig
Hash
了,Array
它完全解决了这个问题。
name = params.dig(:company, :owner, :name)
nil
如果密钥在任何级别丢失,它就会返回。
如果您使用的 Ruby 版本早于 2.3,您可以使用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
功能和清晰度之间的最佳折衷 IMO 是 Raganwald 的andand
. 有了这个,你会做:
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
您可能想研究一种将自动激活添加到 ruby 哈希的方法。以下stackoverflow线程中提到了许多方法:
如果你想进入猴子补丁,你可以做这样的事情
class NilClass
def [](anything)
nil
end
end
然后,如果任何时候嵌套散列之一为 nil,则调用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)
如果是导轨,请使用
params.try(:[], :company).try(:[], :owner).try(:[], :name)
哦等等,那更难看。;-)
我会这样写:
name = params[:company] && params[:company][:owner] && params[:company][:owner][:name]
它不像? Io 中的运算符,但 Ruby 没有。@ThiagoSilveira 的回答也不错,虽然会慢一些。
写一次丑,然后隐藏
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[[:company, :owner, :name]]
或者
params[[:company, :owner, :name]] if params.has_key?([:company, :owner, :name])
反而?
做:
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),并且如果您执行任何与您上方相关的“正常”自动激活内容' 最终会得到一个未找到值的空散列,其评估结果为“真”。
你可以做这样的事情——它只检查定义的值并返回它们。你不能这样设置它们,因为我们无法知道调用是否在任务的 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 中所知道和喜爱的那样。
只是为了提供一个dig
,试试我写的KeyDial gem。这本质上是一个包装器,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
危险但有效:
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”链相似的风格。
TLDR;params&.dig(:company, :owner, :name)
从 Ruby 2.3.0 开始:
您还可以使用&.
称为“安全导航运算符”的方式:params&.[](:company)&.[](:owner)&.[](:name)
。这个绝对安全。
使用dig
onparams
实际上并不安全,如果为 nilparams.dig
则会失败。params
但是,您可以将两者结合为:params&.dig(:company, :owner, :name)
.
因此,以下任何一种都可以安全使用:
params&.[](:company)&.[](:owner)&.[](:name)
params&.dig(:company, :owner, :name)