How do I use the fetch method for nested hash?
EDIT: there is a built-in way now, see this answer.
There is no built-in method that I know of. I have this in my current project
class Hash
def fetch_path(*parts)
parts.reduce(self) do |memo, key|
memo[key.to_s] if memo
end
end
end
# usage
hash.fetch_path('name', 'Mike', 'age')
You can easily modify it to use #fetch
instead of #[]
(if you so wish).
Accessing elements of nested hashes in ruby
The way I usually do this these days is:
h = Hash.new { |h,k| h[k] = {} }
This will give you a hash that creates a new hash as the entry for a missing key, but returns nil for the second level of key:
h['foo'] -> {}
h['foo']['bar'] -> nil
You can nest this to add multiple layers that can be addressed this way:
h = Hash.new { |h, k| h[k] = Hash.new { |hh, kk| hh[kk] = {} } }
h['bar'] -> {}
h['tar']['zar'] -> {}
h['scar']['far']['mar'] -> nil
You can also chain indefinitely by using the default_proc
method:
h = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
h['bar'] -> {}
h['tar']['star']['par'] -> {}
The above code creates a hash whose default proc creates a new Hash with the same default proc. So, a hash created as a default value when a lookup for an unseen key occurs will have the same default behavior.
EDIT: More details
Ruby hashes allow you to control how default values are created when a lookup occurs for a new key. When specified, this behavior is encapsulated as a Proc
object and is reachable via the default_proc
and default_proc=
methods. The default proc can also be specified by passing a block to Hash.new
.
Let's break this code down a little. This is not idiomatic ruby, but it's easier to break it out into multiple lines:
1. recursive_hash = Hash.new do |h, k|
2. h[k] = Hash.new(&h.default_proc)
3. end
Line 1 declares a variable recursive_hash
to be a new Hash
and begins a block to be recursive_hash
's default_proc
. The block is passed two objects: h
, which is the Hash
instance the key lookup is being performed on, and k
, the key being looked up.
Line 2 sets the default value in the hash to a new Hash
instance. The default behavior for this hash is supplied by passing a Proc
created from the default_proc
of the hash the lookup is occurring in; ie, the default proc the block itself is defining.
Here's an example from an IRB session:
irb(main):011:0> recursive_hash = Hash.new do |h,k|
irb(main):012:1* h[k] = Hash.new(&h.default_proc)
irb(main):013:1> end
=> {}
irb(main):014:0> recursive_hash[:foo]
=> {}
irb(main):015:0> recursive_hash
=> {:foo=>{}}
When the hash at recursive_hash[:foo]
was created, its default_proc
was supplied by recursive_hash
's default_proc
. This has two effects:
- The default behavior for
recursive_hash[:foo]
is the same asrecursive_hash
. - The default behavior for hashes created by
recursive_hash[:foo]
'sdefault_proc
will be the same asrecursive_hash
.
So, continuing in IRB, we get the following:
irb(main):016:0> recursive_hash[:foo][:bar]
=> {}
irb(main):017:0> recursive_hash
=> {:foo=>{:bar=>{}}}
irb(main):018:0> recursive_hash[:foo][:bar][:zap]
=> {}
irb(main):019:0> recursive_hash
=> {:foo=>{:bar=>{:zap=>{}}}}
Use an Array to access nested Hash keys
You could make use of the newer method Hash#dig
(ruby 2.3+) to access the nested hash, and then set the value on that:
# ideally this might be a method on Hash, so you wouldn't need to pass it in
def deep_set(hash, path, value)
*path, final_key = path
to_set = path.empty? ? hash : hash.dig(*path)
return unless to_set
to_set[final_key] = value
end
hash = {
"foo" => {
"bar" => { }
}
}
deep_set(hash, ["foo", "bar", "baz"], "xxxxxx")
deep_set(hash, ["baz"], "yyyyyy")
deep_set(hash, ["foo", "baz", "bar"], "zzzzzz")
puts hash
# => {"foo"=>{"bar"=>{"baz"=>"xxxxxx"}}, "baz"=>"yyyyyy"}
Nested hash defined?()
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
Ruby nested hash fetching dynamically
I'm not sure a Hash is really the best data structure here. You're trying to use it to represent a tree, which is fine and all, but it might be a bit clearer if you just explicitly made it a tree:
class Tree
attr_reader :name, :uri, :children, :parent
def initialize(name, uri, *children)
@children = children
@name, @uri = name, uri
end
def <<(child)
@children << child
end
def find(name)
each_branch.detect {|branch| branch.name == name }
end
def leaf?
@children.empty?
end
# The parameter `i` just decides whether or not to include self.
def each_branch( i=true, &blk )
enum = Enumerator.new do |y|
y.yield self if i
@children.each do |c|
next unless c.is_a? Tree
y.yield c
c.each_branch( false ).each {|b| y.yield b }
end
end
block_given? ? enum.each( &blk ) : enum
end
# This yields each leaf and its parent.
def each_leaf( parent=self, &blk )
enum = Enumerator.new do |y|
@children.each do |c|
if !c.leaf?
c.each_leaf( c ).each do |l,p|
y.yield l, p
end
else y.yield c, parent
end
end
end
block_given? ? enum.each( &blk ) : enum
end
end
(I just borrowed those enumerators from a tree structure I'd made before - the each_leaf
method might be helpful too, and you can check for the class not being Tree
instead of leaf?
returning true
if you had a tree structure that could contain other objects, like strings).
Then you could do:
root_tree = Tree.new "Farming", "farming"
root_tree << Tree.new( "Organic Gas", "organic_gas" )
gas = root_tree.find "Organic gas"
gas << Tree.new(...)
I think this is just a case of finding the right data structure for the job. Even if the tree method were a bit less efficient, it's clearer what's going on, and will probably lead to fewer bugs in your dynamic code down the track.
If the problem is that you don't want the original tree to be modified, only copied, then just redefine:
class Tree
attr_accessor :children
def <<(child)
new_tree = self.dup
new_tree.children = @children + [child]
new_tree
end
end
That way, you retain the original tree but return a new tree with the extra child added.
Unable to fetch deeply nested hash value
There is a lot of things going on in your code sample. I tried to split in parts and restructure it. It does not do the same as your code but I think it should get you started and perhaps you can come back when you have a more specific question.
Note that I did not use hashie, I think that accessing some deeply nested hash structures in a few places does not justify adding a new library to a project.
Questions/Ideas/Hints:
- are prices Integers or Floats?
- Is the JSON consistent (all elements present all the time?)
- Are you using Ruby 2.3? Then look into
Hash#dig
- Why did you prettify the JSON keys? Does not make sense to me as you build
Product
objects to work with anyway? - Unless there are performance issues i would convert all products to Ruby objects first and filter then. Just easier and more readable.
Code
Product (same as yours)
Product = Struct.new(:category, :name, :image, :price, :description)
JsonProductBuilder converts the parsed JSON to Product Objects.
class JsonProductBuilder
def initialize(json)
@json = json
end
def call
json.fetch('products', []).map do |item|
Product.new(
extract_category(item),
item['name'],
item.fetch('productImage', {})['url'],
extract_price(item),
item['description']
)
end
end
private
attr_reader :json
def extract_category(item)
field = item['fields'].find do |field|
field['name'] == 'productGroup'
end
field['value'] if field
end
def extract_price(item)
offer = item['offers'].first
history = offer['priceHistory'].first
value = history['price']['value']
Integer(value) # Or use Float?
end
end
CategoryFilter returns a limited subset of the products. You can easily add other filters and combine them. Perhaps you might want to look into lazy
for performance improvements.
class CategoryFilter
def initialize(products, *categories)
@products = products
@categories = categories
end
def call
products.select do |product|
categories.include?(product.category)
end
end
private
attr_reader :products, :categories
end
Use it like this:
limit = 10
categories = ['laptop', 'something']
params = {
q: categories.join(','),
limit: limit,
}
paramsString = params.map do |key, value|
"#{key}=#{value}"
end.join(';')
response = RestClient.get(
"http://api.tradedoubler.com/1.0/products.json;#{paramsString}?token=#{token}"
)
json = JSON.parse(response)
products = JsonProductBuilder.new(json).call
puts products.size
products = CategoryFilter.new(products, 'Klær', 'Sko', 'Jeans').call
puts products.size
products.each do |product|
puts product.to_h
end
Accessing values in nested hash
To start, you need to understand what iterating over a hash will give you.
Consider this:
exp = {
fam: {cty: "bk", ins: 3},
spec: {cty: "man", ins: 2},
br: {cty: "qns", ins: 1},
aha: {cty: "man", ins: 0}
}
exp.map { |e, c, value| [e, c, value] }
# => [[:fam, {:cty=>"bk", :ins=>3}, nil], [:spec, {:cty=>"man", :ins=>2}, nil], [:br, {:cty=>"qns", :ins=>1}, nil], [:aha, {:cty=>"man", :ins=>0}, nil]]
This is basically what you're doing as you loop and Ruby passes the block the key/value pairs. You're telling Ruby to give you the current hash key in e
, the current hash value in c
and, since there's nothing else being passed in, the value
parameter becomes nil
.
Instead, you need a block variable for the key, one for the value:
exp.map { |k, v| [k, v] }
# => [[:fam, {:cty=>"bk", :ins=>3}], [:spec, {:cty=>"man", :ins=>2}], [:br, {:cty=>"qns", :ins=>1}], [:aha, {:cty=>"man", :ins=>0}]]
Notice that the nil values are gone.
Rewriting your code taking that into account, plus refactoring it for simplicity:
exp = {
fam: {cty: 'bk', ins: 3},
spec: {cty: 'man', ins: 2},
br: {cty: 'qns', ins: 1},
aha: {cty: 'man', ins: 0}
}
exp.each do |k, v|
if v[:cty] == 'man'
puts k
end
end
# >> spec
# >> aha
Now it's returning the keys you want, so it becomes easy to grab the entire hashes. select
is the appropriate method to use when you're trying to locate specific things:
exp = {
fam: {cty: 'bk', ins: 3},
spec: {cty: 'man', ins: 2},
br: {cty: 'qns', ins: 1},
aha: {cty: 'man', ins: 0}
}
e = exp.select { |k, v| v[:cty] == 'man' }
# => {:spec=>{:cty=>"man", :ins=>2}, :aha=>{:cty=>"man", :ins=>0}}
Older versions of Ruby didn't maintain hash output from the hash iterators so we'd have to coerce back to a hash:
e = exp.select { |k, v| v[:cty] == 'man' }.to_h
# => {:spec=>{:cty=>"man", :ins=>2}, :aha=>{:cty=>"man", :ins=>0}}
ruby select element nested hash same keys
You can do as
result["Results"]["series"][0]["data"].find(->(){ {} }) do |hash|
hash[period] == 'M06'
end.fetch(value, "period not found")
#find
- Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns its result when it is specified, or returns nil otherwise.
Thus for any reason if 'M06'
value not found for the key period from any of the hash, then the #find
will call the argument I passed to it, like ->() { {} }.call
, and return the empty hash, otherwise if 'M06'
found for the key 'period
of any hash,then that hash will be returned. On this returned hash, I am calling Hash#fetch
method.
Example to explain this :-
#!/usr/bin/env ruby
array = {a: 1, b: 2}, { a: 4, b: 11}
def fetch_value(array, search_key, search_value, fetch_key)
array.find(->(){ {} }) do |h|
h[search_key] == search_value
end.fetch(fetch_key, "#{search_value} is not found for #{search_key}.")
end
fetch_value(array, :a, 11, :b) # => "11 is not found for a."
fetch_value(array, :a, 4, :b) # => 11
How to get value of key or key presence from nested hash?
%w[a c e f].inject(a, &:fetch) # => 3
%w[a c e x].inject(a, &:fetch) # > Error key not found: "x"
%w[x].inject(a, &:fetch) # => Error key not found: "x"
Or, to avoid errors:
%w[a c e f].inject(a, &:fetch) rescue "Key not found" # => 3
%w[a c e x].inject(a, &:fetch) rescue "Key not found" # => Key not found
%w[x].inject(a, &:fetch) rescue "Key not found" # => Key not found
Related Topics
Ruby on Rails Add a Column After a Specific Column Name
Rails: How to Print a Decimal as a Percent
What Is the Purpose of the Enumerator Class in Ruby
How to Delete Some Folders of Nokogiri and Capybara-Webkit Inside of My Rvm Gemset
Get, or Calculate the Entropy of an Image with Ruby and Imagemagick
Configuration in Allure to Cucumber in Ruby
How Does Ruby Allow a Method and a Class with the Same Name
Alphabetize Arabic and Japanese Text That Is in Unicode
Xlsx Compressed by Rubyzip Not Readable by Excel
Trying to Compare Two Text Files, and Create a Third Based on Information
How to Stub a Method of an Included Module with Rspec
Why Is Ruby String.Hash Inconsistent Across MAChines
How to Remove Xcode 4.2 and Install 4.1 to Develop Ruby/Rails on Osx Lion
How to Extend Redcarpet to Support Auto Linking User Mentions
Authlogic Perishable_Token Resets on Every Request
Expected #Count to Have Changed by 1, But Was Not Given a Block