Converting a Hash into a Nested Hash

How would I parse a flat Ruby hash in to a nested hash?

First you can split up the hash into multiple hashes according to their prefix number (feel free to run this to see the return val)

groups = input.
group_by { |k,v| k.match(/builder_rule_(\d+)/)[1] }.
transform_values(&:to_h)

at this point, creating the inner objects is easier if you just use some hard-coding to build the key-valls:

result = groups.each_with_object({}) do |(prefix, hash), memo|
memo[prefix] = {
"filter" => hash["builder_rule_#{prefix}_filter"],
"operator" => hash["builder_rule_#{prefix}_operator"],
"values" => hash.select do |key, val|
key =~ /builder_rule_#{prefix}_value/
end.sort_by { |key, val| key }.map { |(key, val)| val }
}
end

It might possibly be confusing what .sort_by { |key, val| key }.map { |(key, val)| val } means. I can spell it out:

  • hash.select { |key, val| key =~ /builder_rule_#{prefix}_value/ } gets the key-vals which are going to be used for the "values" array. It returns a hash.
  • .sort_by { |key, val| key } turns the hash into an array of [key, val] tuples , sorted by the key. This is so that the values appear in the correct order.
  • .map { |(key, val)| val } turns the nested array into a single-level array, discarding the keys

You could also use sort_by(&:first).map(&:second), though you need active support to use Array#second

Converting a hash into a nested hash

Here's an iterative solution, a recursive one is left as an exercise to the reader:

def convert(h={})
ret = {}
h.each do |k,v|
node = ret
k[0..-2].each {|x| node[x]||={}; node=node[x]}
node[k[-1]] = v
end
ret
end

convert(your_hash) # => {:f=>4, :a=>{:b=>{:c=>1, :d=>2}, :e=>3}}

How to trasform all values in a nested hash?

Interesting to learn of the deep_merge approach taken in the answer by "The F". Here is another approach which requires adding a few helper methods.

First, the helper methods:

From the top answer here (converting-a-nested-hash-into-a-flat-hash):

def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end

From a Github repo called ruby-bury (this functionality was proposed to Ruby core, but rejected)

class Hash
def bury *args
if args.count < 2
raise ArgumentError.new("2 or more arguments required")
elsif args.count == 2
self[args[0]] = args[1]
else
arg = args.shift
self[arg] = {} unless self[arg]
self[arg].bury(*args) unless args.empty?
end
self
end
end

And then a method tying it together:

def change_all_values(hash, &blk)    
# the next line makes the method "pure functional"
# but can be removed otherwise.
hash = Marshal.load(Marshal.dump(hash))

flat_hash(hash).each { |k,v| hash.bury(*(k + [blk.call(v)])) }
hash
end

A usage example:

irb(main):063:0> a = {a: 1, b: { c: 1 } }
=> {:a=>1, :b=>{:c=>1}}
irb(main):064:0> b = change_all_values(a) { |val| val + 1 }
=> {:a=>2, :b=>{:c=>2}}
irb(main):066:0> a
=> {:a=>1, :b=>{:c=>1}}

How to convert a string into a nested hash

First of all, you have to split the string. You can then use inject to build the structure. Because the hash is built from the inside out, we have to reverse the initial array:

'foo.bar.baz'.split('.')  #=> ["foo", "bar", "baz"]
.reverse #=> ["baz", "bar", "foo"]
.inject('qux') { |memo, key| { key.to_sym => memo } }
#=> {:foo=>{:bar=>{:baz=>"qux"}}}

It might not be obvious how this works. On the first invocation, memo is 'qux' and key is 'baz'. The block turns this into {:baz=>'qux'} which then becomes the new memo.

Step by step:

memo                    key                result
--------------------------------------------------------------
'qux' 'baz' {:baz=>'qux'}
{:baz=>'qux'} 'bar' {:bar=>{:baz=>'qux'}}
{:bar=>{:baz=>'qux'}} 'foo' {:foo=>{:bar=>{:baz=>'qux'}}}

Insert new key value into nested hash in Ruby

You need to construct a new array from your existing:

photos = [{"id": "111","photo": "http://photo.com/111.jpeg"}, {"id": "222",  "photo": "http://photo.com/222.jpeg"}]

new_photos = photos.map do |photo|
if photo[:id] == '111'
photo.merge(type: 'Profile')
else
photo
end
end

How to convert hash with keys representing nesting into a nested hash

After some research I found a way to parse nested query keys using http://apidock.com/rails/Rack/Utils/parse_nested_query:

Rack::Utils.parse_nested_query('item[0][size]')
=> {
"item" => {
"0" => {
"size" => nil
}
}
}

So it's now possible to do:

items_string = item_hash.to_a.map { |row| row.join('=') }.join('&')
result = Rack::Utils.parse_nested_query(items_string)

=> {
"item" => {
"0" => {
"size" => "12",
"count" => "1"
}
}
}

Convert array into nested hash for each element

First, a function to loop through the array and split those strings into an array which is much easier to work with.

def nest_array(array)
return array.each_with_object({}) do |string, hash|
values = string.split(/,/)

do_nesting(values, hash)
end
end

Then a recursive function to deal with each individual entry. ['a'] becomes { 'a' => nil }, ['a', 'b'] becomes { 'a' => 'b' } and ['a', 'b', 'c'] recurses to make a new hash from ['b', 'c'].

def do_nesting(values, hash)
if values.size <= 2
hash[values[0]] = values[1]
else
hash[values.shift] = do_nesting(values, {})
end

return hash
end


Related Topics



Leave a reply



Submit