Search code examples
ruby-on-railsrubyruby-hash

How to iterate over deep nested hash without known depth in Ruby


I have multiple YAML (localization) files. I parse them and convert to hash in Ruby.

For example this is one of them:

hello: Hallo
messages:
  alerts:
    yay: Da!
    no: Nein
  deep:
    nested:
      another:
        level:
          hi: Hi!
test: Test!

Basically, this is look like a locale file in Rails App using YAML.

What I want to do is iterate this Hash recursively and get key and value. So that i can translate values one-by-one from API Endpoint like Google Translate. I want to keep nested hashes in same schema so that Rails can find by keys.

I know i can use nested loops but there is no guarantee that nested hashes is a known number of. How can I iterate this hash recursively so i can manipulate values (translate/replace)?

Expected Result: (after used translation service from API call)

hello: Hello
messages:
  alerts:
    yay: Yup!
    no: No
  deep:
    nested:
      another:
        level:
          hi: Hi!
test: Test!

What I've tried so far:

hash = YAML.load('de.yml') # parse source Deutsch locale 
new_hash = {}

hash.each |key, value| do
  new_hash[key] = translate_func(value) # here... translate value then assign very same key including parents.

  # Do more loops....
end

# Now write this new_hash to yaml file...

But this only manipulate hello only. To get work with others I have to make a loop. But how many keys are nested is unknown.

How can I iterate over all values of locale hash and keep the schema intact?

And if possible but not mandatory, I would be very happy if we can keep the order of keys on final result. That would be awesome to find missing keys later when manually reviewed.

I am very new to ruby. I am using Ruby 2.7.2

Conclusion / Resolve

All answers are correct and I love all of them. However, I would like to be able to control both keys and values. Not just transform by values. Therefore, I accepted an answer that fits to my needs. I was able to do my intention with selected answer.


Solution

  • So, you want to parse recursively until there are no more levels to parse into.

    It’s super common in software and referred to as “recursion”. Have a search around to learn more about it - it’ll come up again and again in your journey. Welcome to ruby btw!

    As for your actual current problem. Have a read of https://mrxpalmeiras.wordpress.com/2017/03/30/how-to-parse-a-nested-yaml-config-file-in-python-and-ruby/

    But also, consider the i18n gem. See this answer https://stackoverflow.com/a/51216931/1777331 and the docs for the gem https://github.com/ruby-i18n/i18n This might fix your problem of handling internationalisation without you having to get into the details of handling yaml files.