Access Ruby Hash Using Dotted Path Key String
Just split on a dot in the path and iterate over this to find the right hash?
path.split(".").inject(hash) { |hash, key| hash[key] }
Alternatively you can build a new hash by iterating recursively over the whole structure:
def convert_hash(hash, path = "")
hash.each_with_object({}) do |(k, v), ret|
key = path + k
if v.is_a? Hash
ret.merge! convert_hash(v, key + ".")
else
ret[key] = v
end
end
end
Ruby dot notation to nested hash keys
Try this
f = "root/sub-1/sub-2/file"
f.split("/").reverse.inject{|a,n| {n=>a}} #=>{"root"=>{"sub-1"=>{"sub-2"=>"file"}}}
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"}
How to transform dot-notation string keys in a Hash into a nested Hash?
This code may need to be refactored but it works for the input you have given.
hash = {
:axis => [1,2],
"coord.x" => [12,13],
"coord.y" => [14,15],
}
new_hash = {}
hash.each do |key, val|
new_key, new_sub_key = key.to_s.split('.')
new_key = new_key.to_sym
unless new_sub_key.nil?
new_sub_key = new_sub_key.to_sym
new_hash[new_key] = {} if new_hash[new_key].nil?
new_hash[new_key].merge!({new_sub_key => val})
else
new_hash.store(key, val)
end
end
new_hash # => {:axis=>[1, 2], :coord=>{:x=>[12, 13], :y=>[14, 15]}}
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=>{}}}}
Find key/value pairs deep inside a hash containing an arbitrary number of nested hashes and arrays
Here's a simple recursive solution:
def nested_hash_value(obj,key)
if obj.respond_to?(:key?) && obj.key?(key)
obj[key]
elsif obj.respond_to?(:each)
r = nil
obj.find{ |*a| r=nested_hash_value(a.last,key) }
r
end
end
h = { foo:[1,2,[3,4],{a:{bar:42}}] }
p nested_hash_value(h,:bar)
#=> 42
Unable to use dot syntax for ruby hash
Hash
does not have dot-syntax for it's keys. OpenStruct
does:
require 'ostruct'
hash = {:name => 'John'}
os = OpenStruct.new(hash)
p os.name #=> "John"
NOTE: Does not work with nested hashes.
Build paths of edge keys in nested hashes in ruby
def recurse(h)
h.flat_map do |k,v|
if v.is_a?(Hash)
recurse(v).map { |str| "%s.%s" % [k.to_s, str] }
else
k.to_s
end
end
end
h = {a: {m: {b: 2, c:1}, d: {e: {f: nil}, g: 3}}}
recurse(h)
#=> ["a.m.b", "a.m.c", "a.d.e.f", "a.d.g"]
h = {a: {m: {b: 2, c:1}, d: 5 }, e: {f: {g: nil}, h: 3}}
recurse(h)
#=> ["a.m.b", "a.m.c", "a.d", "e.f.g", "e.h"]
hash['key'] to hash.key in Ruby
>> require 'ostruct'
=> []
>> foo = {'bar'=>'baz'}
=> {"bar"=>"baz"}
>> foo_obj = OpenStruct.new foo
=> #<OpenStruct bar="baz">
>> foo_obj.bar
=> "baz"
>>
Classic hash to dot-notation hash
Here's the cleanest solution I could come up with right now:
def dot_it(object, prefix = nil)
if object.is_a? Hash
object.map do |key, value|
if prefix
dot_it value, "#{prefix}.#{key}"
else
dot_it value, "#{key}"
end
end.reduce(&:merge)
else
{prefix => object}
end
end
Test:
input = {a: {b: {"1" => 1, "2" => 2}, d: "Something"}, b: {c: 1}}
p dot_it input
Output:
{"a.b.1"=>1, "a.b.2"=>2, "a.d"=>"Something", "b.c"=>1}
Related Topics
How to Programmatically Check If a Certificate Has Been Revoked
Is It a Good Idea to Purge Old Rails Migration Files
How to Write Bom Marker to a File in Ruby
Stack Level Too Deep When Using Carrierwave Versions
Could Not Find Rake with Bundle Exec
Trying to Set Up Amazon's S3 Bucket: 403 Forbidden Error & Setting Permissions
How to Modify a Text File in Ruby
Shorter Way to Pass Every Element of an Array to a Function
How to Quickly Reorder a Ruby Array Given an Order
Determine the Class to Which a Method Belongs in Rails
Ruby Variable as Same Object (Pointers)
Devise Raises Error with Rails 4.2 Upgrade
Could Not Find Rake-10.0.4 in Any of the Sources (Bundler::Gemnotfound)
Ssl_Connect Syscall Returned=5 Errno=0 State=Sslv2/V3 Read Server Hello A
Database Cleaner Not Working in Minitest Rails
Clicking Link with JavaScript in Mechanize
Generate Array of Numbers That Fit to a Probability Distribution in Ruby
How to Evaluate a Date Difference in Years, Months and Days (Ruby)