Iterating through a ruby nested hash with nils? [duplicate]
-
19-04-2021 - |
Pergunta
Let say I'm getting back a JSON nested hash (or array of hashes) from an API
@example = {"results" = > {{"poop" => "shoop"},{"foo" => {"shizz" => "fizz", "nizzle"=>"bizzle"}}}
YAML markup for the nested hash above
- poop: shoop
- foo:
shizz: fizz
nizzle: bizzle
Now lets go make a db entry with ActiveRecord from the hash. This works fine.
Thing.create!(:poop => @example["results"]["poop"],
:shizz => @example["results"]["foo"]["shizz"],
:nizzle=> @example["results"]["foo"]["nizzle"])
But what if 'foo' is empty or nil? For example, if an API result has a "person" hash with "first name","last name" # etc, the "person" hash will usually be empty if there is no data, which means the hashes inside it don't exist.
@example = {"results" = > {{"poop" => "shoop"},{"foo" => nil }}
Thing.create!(:poop => @example["results"]["poop"],
:shizz => @example["results"]["foo"]["shizz"],
:nizzle=> @example["results"]["foo"]["nizzle"])
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
What's the best way to handle this?
Solução
I came accross a nil
sensitive Hash#get
method a while back.
class Hash
def get(key, default=nil)
key.split(".").inject(self){|memo, key_part| memo[key_part] if memo.is_a?(Hash)} || default
end
end
h = { 'a' => { 'b' => { 'c' => 1 }}}
puts h.get "a.b.c" #=> 1
puts h.get "a.b.c.d" #=> nil
puts h.get "not.here" #=> nil
It's pretty handy for this sort of JSON drilling.
Otherwise you have to do stuff like this:
h['a'] && h['a']['b'] && h['a']['b']['c']
And that just sucks.
Outras dicas
If you're using rails (not sure if it's in ruby 1.9):
h = {"a"=>1}
h.try(:[],"a") #1
h.try(:[],"b") #nil
h2 = {"c"=>{"d"=>1}}
h2.try(:[],"c").try(:[],"d") #1
h2.try(:[],"a").try(:[],"foo") #nil
# File activesupport/lib/active_support/core_ext/object/try.rb, line 28
def try(*a, &b)
if a.empty? && block_given?
yield self
else
__send__(*a, &b)
end
end
I went ahead and started passing all Hash results into a Hashie Mash. That way they behave like Ruby objects and respond to nils like a champ!
Ruby 2.3.0 introduced a new method called dig
on both Hash
and Array
that solves this problem entirely.
value = hash.dig(:a, :b)
It returns nil
if the key is missing at any level.