Reputation: 195
I have an Array of strings which represent a path of nested hash keys e.g.
["foo", "bar", "baz"]
I want to create a function which takes in the Array and a value and sets the value for the key provided in the Array, so when I call it with the example, it sets the value for
Hash["foo"]["bar"]["baz"]
.
Is there a method that can do that. Is there a way to chain the elements of an Array into calls for hash keys with #inject ?
Any help is highly appreciated.
To specify the question:
I have the following code:
def anonymize_data_hash(data, path=[])
if data.is_a?(Hash)
data.each do |key, value|
anonymize_data_hash(value, path + [key])
end
elsif data.is_a?(Array)
data.each do |value|
anonymize_data_hash(value, path)
end
else
path = path.clone
key = path.shift
path = (path + [data]).map(&:to_s)
send("#{key}")[path] = "xxxxxx"
save
end
end
the anonymize_data_hash method sends an method(attribute) call to a mode which is a serialized hash. Path is an array of strings. In order make the above function work I need to turn the array of string into a nested Hash call.
The Hash already exists I need to access it with the values given in the Array. Thank You for Your help.
Upvotes: 0
Views: 2455
Reputation: 1238
Simple Lime's solution requires a well-formatted hash to begin with. If addition to the hash depends on that, you highly depend on the input data and its order to determine if a value is added or not (e.g. "zzzzzz" in the example). You might want to (re-)define the hash's default_proc
to create the structure of the hash on the fly. Here is an example:
def nest(array, value)
hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
hash.dig(*array[0..-2])[array.fetch(-1)] = value
hash
end
nest ["foo", "bar", "baz"], :test
# => {"foo"=>{"bar"=>{"baz"=>:test}}}
Since the hash already exists, I suggest proper initialisation of its default_proc
.
hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
If that's not possible, you can still overwrite it later as well
hash = {} # unchange initialisation
hash.default_proc = -> (h, k) { h[k] = Hash.new(&h.default_proc) }
def nest(hash, array, value)
hash.dig(*array[0..-2])[array.fetch(-1)] = value
hash
end
nest hash, ["foo", "bar", "baz"], :test
# => {"foo"=>{"bar"=>{"baz"=>:test}}}
I hope you find this helpful too.
Upvotes: 2
Reputation: 11050
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"}
Upvotes: 9