How to Use the Fetch Method for Nested Hash

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:

  1. The default behavior for recursive_hash[:foo] is the same as recursive_hash.
  2. The default behavior for hashes created by recursive_hash[:foo]'s default_proc will be the same as recursive_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



Leave a reply



Submit