Flattening Nested Hash to a Single Hash with Ruby/Rails

Flattening nested hash to a single hash with Ruby/Rails

You could do this:

def flatten_hash(hash)
hash.each_with_object({}) do |(k, v), h|
if v.is_a? Hash
flatten_hash(v).map do |h_k, h_v|
h["#{k}.#{h_k}".to_sym] = h_v
end
else
h[k] = v
end
end
end

flatten_hash(:foo => "bar",
:hello => {
:world => "Hello World",
:bro => "What's up dude?",
},
:a => {
:b => {
:c => "d"
}
})
# => {:foo=>"bar",
# => :"hello.world"=>"Hello World",
# => :"hello.bro"=>"What's up dude?",
# => :"a.b.c"=>"d"}

How to flatten nested hash in Ruby

REGIONS.values.reduce(&:merge)

How do I flatten a nested hash, recursively, into an array of arrays with a specific format?

As hinted at in the comments:

Looks pretty straightforward. Descend into hashes recursively, taking note of keys you visited in this branch. When you see an array, no need to recurse further. Append it to the list of keys and return

Tracking is easy, just pass the temp state down to recursive calls in arguments.

I meant something like this:

def tree_flatten(tree, path = [], &block)
case tree
when Array
block.call(path + tree)
else
tree.each do |key, sub_tree|
tree_flatten(sub_tree, path + [key], &block)
end
end
end

tree_flatten(tree_def) do |path|
p path
end

This code simply prints each flattened path as it gets one, but you can store it in an array too. Or even modify tree_flatten to return you a ready array, instead of yielding elements one by one.

Flattening nested hash to an array

Recursive.

def flatten_nested_hash(categories)
categories.flat_map{|k, v| [k, *flatten_nested_hash(v)]}
end

Defining it on the Hash class.

class Hash
def flatten_nested; flat_map{|k, v| [k, *v.flatten_nested]} end
end

How to flatten a hash, making each key a unique value?

Interesting question!

Theory

Here's a recursive method to parse your data.

  • It keeps track of which keys and indices it has found.
  • It appends them in a tmp array.
  • Once a leaf object has been found, it gets written in a hash as value, with a joined tmp as key.
  • This small hash then gets recursively merged back to the main hash.

Code

def recursive_parsing(object, tmp = [])
case object
when Array
object.each.with_index(1).with_object({}) do |(element, i), result|
result.merge! recursive_parsing(element, tmp + [i])
end
when Hash
object.each_with_object({}) do |(key, value), result|
result.merge! recursive_parsing(value, tmp + [key])
end
else
{ tmp.join('_') => object }
end
end

As an example:

require 'pp'
pp recursive_parsing(data)
# {"Name"=>"Kim Kones",
# "License Number"=>"54321",
# "Details_Name"=>"Kones, Kim",
# "Details_Licenses_1_License Type"=>"PT",
# "Details_Licenses_1_License Number"=>"54321",
# "Details_Licenses_2_License Type"=>"Temp",
# "Details_Licenses_2_License Number"=>"T123",
# "Details_Licenses_3_License Type"=>"AP",
# "Details_Licenses_3_License Number"=>"A666",
# "Details_Licenses_3_Expiration Date"=>"12/31/2020"}

Debugging

Here's a modified version with old-school debugging. It might help you understand what's going on:

def recursive_parsing(object, tmp = [], indent="")
puts "#{indent}Parsing #{object.inspect}, with tmp=#{tmp.inspect}"
result = case object
when Array
puts "#{indent} It's an array! Let's parse every element:"
object.each_with_object({}).with_index(1) do |(element, result), i|
result.merge! recursive_parsing(element, tmp + [i], indent + " ")
end
when Hash
puts "#{indent} It's a hash! Let's parse every key,value pair:"
object.each_with_object({}) do |(key, value), result|
result.merge! recursive_parsing(value, tmp + [key], indent + " ")
end
else
puts "#{indent} It's a leaf! Let's return a hash"
{ tmp.join('_') => object }
end
puts "#{indent} Returning #{result.inspect}\n"
result
end

When called with recursive_parsing([{a: 'foo', b: 'bar'}, {c: 'baz'}]), it displays:

Parsing [{:a=>"foo", :b=>"bar"}, {:c=>"baz"}], with tmp=[]
It's an array! Let's parse every element:
Parsing {:a=>"foo", :b=>"bar"}, with tmp=[1]
It's a hash! Let's parse every key,value pair:
Parsing "foo", with tmp=[1, :a]
It's a leaf! Let's return a hash
Returning {"1_a"=>"foo"}
Parsing "bar", with tmp=[1, :b]
It's a leaf! Let's return a hash
Returning {"1_b"=>"bar"}
Returning {"1_a"=>"foo", "1_b"=>"bar"}
Parsing {:c=>"baz"}, with tmp=[2]
It's a hash! Let's parse every key,value pair:
Parsing "baz", with tmp=[2, :c]
It's a leaf! Let's return a hash
Returning {"2_c"=>"baz"}
Returning {"2_c"=>"baz"}
Returning {"1_a"=>"foo", "1_b"=>"bar", "2_c"=>"baz"}

Flatten array of nested hashes

If you don't mind modifying the array in-place then you could say:

array.each { |h| h.merge!(h.delete('percentiles')) }

If you're not sure that all the hashes have 'percentiles' keys then you could say:

# Explicitly check
array.each { |h| h.merge!(h.delete('percentiles')) if(h.has_key?('percentiles')) }
# Convert possible `nil`s to `{ }`
array.each { |h| h.merge!(h.delete('percentiles').to_h) }
# Filter before merging
array.select { |h| h.has_key?('percentiles') }.each { |h| h.merge!(h.delete('percentiles')) }

If you want to flatten all hash values then you can do things like this:

array.each do |h|
h.keys.each do |k|
if(h[k].is_a?(Hash))
h.merge!(h.delete(k))
end
end
end

If you don't want to modify the hashes inside the array then variations on:

flat = array.map(&:dup).each { |h| h.merge!(h.delete('percentiles')) }

flat = array.map do |e|
e.each_with_object({}) do |(k, v), h|
if(v.is_a?(Hash))
h.merge!(v)
else
h[k] = v
end
end
end

Flatten nested hashes in an array - Ruby

input.flat_map do |h|
# split the input into group and tests in the first place
h.partition { |k, v| k == "Group" }.reduce(&:product)
end.map do |(name, group), (_, data)|
# due to product above each element is a 2×2 array
# first item is the Group, the second is the TestN
{name => group}.merge(data.merge(data.delete("AdditionalInfo")))
end

Flattening nested hash to an array

Recursive.

def flatten_nested_hash(categories)
categories.flat_map{|k, v| [k, *flatten_nested_hash(v)]}
end

Defining it on the Hash class.

class Hash
def flatten_nested; flat_map{|k, v| [k, *v.flatten_nested]} end
end

Flatten a nested hash in Ruby on Rails

data = { ... }

h = data['Result'].first
h.merge!(h.delete('Links').first)

Converting a nested hash into a flat hash

Another way:

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

h = { :a => { :b => { :c => 1,
:d => 2 },
:e => 3 },
:f => 4 }

flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}


Related Topics



Leave a reply



Submit