DEV Community

loading...

Flattening a nested hash to a single hash in Ruby

Hideaki Ishii
I'm a Software Engineer, who mainly uses Ruby / Rails, JavaScript / TypeScript, Golang, etc.
Updated on ・2 min read

When using Ruby, you may sometimes need to flatten a nested hash like:

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

to a single hash like:

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

Today, I introduce a way to achieve it referring to a post in Stack Overflow.

Let’s implement

def flatten_hash_from(hash)
  hash.each_with_object({}) do |(key, value), memo|
    next flatten_hash_from(value).each do |k, v|
      memo["#{key}.#{k}".intern] = v
    end if value.is_a? Hash
    memo[key] = value
  end
end

Using Enumberable#each_with_object recursively helps us achieve it.
Let’s deep dive into how it works.

How it works

  1. First, key = :foo, value = "bar" is given. the value is not a hash, so memo becomes { :foo => "bar" } and the process continues.

  2. key = :hello, value = { :world => "Hello World", :bro => "What's up dude?" } is given. The value is a hash, so flatten_hash_from is invoked again with the value.

  3. After STEP 2, memo is {} again, which is different from the one in STEP 1. Then, key = :world, value = "Hello World" is given, which is not a hash, so memo becomes { :world => "Hello World" }. Similarly, key = :bro is given next, then memo becomes { :world => "Hello World", :bro => "What's up dude?" } and the result is returned to STEP 2.

  4. From STEP 3, the process goes back to each part with key = :hello. By memo["#{key}.#{k}".intern] = v, memo becomes { :foo => "bar", :"hello.world" => "Hello World", :"hello.bro" => "What's up dude?" }.

  5. Next, key = :a, value = { :b => { :c => "d" } } is given. The value is a hash then, flatten_hash_from is invoked recursively like STEP2.

  6. flatten_hash_from is invoked with key = :b, value = { :c => "d" }.

  7. { :c => "d" } is returned to STEP 6.

  8. { :"b.c" => "d" } is returned to STEP 5.

  9. From STEP 5, the process goes back to each part with key = :a and { :"a.b.c" => "d" } is added to memo. It turns out memo is { :foo => "bar", :"hello.world" => "Hello World", :"hello.bro" => "What's up dude?", :"a.b.c" => "d" }, then the process completed.

References

Discussion (0)