Search code examples
rubynested-loopskey-value

Ruby: How to create nested key/value relationship


I am trying to create a function that outputs the key and value for all entries of that JSON as described below so I can use it to send information similar to this:

key = "id", value = 1
key = "mem/stat1", value = 10
key = "more_stats/extra_stats/stat7", value = 5

Example JSON :

my_json =
    {
      "id": 1,
      "system_name": "System_1",
      "mem" : {
          "stat1" : 10,
          "stat2" : 1056,
          "stat3" : 10563,
      },
     "other_stats" : {
        "stat4" : 1,
        "stat5" : 2,
        "stat6" : 3,
      }, 
      "more_stats" : {
        "name" : "jdlfjsdlfjs",
        "os" : "fjsalfjsl",
        "error_count": 3
        "extra_stats" : {
            "stat7" : 5,
            "stat8" : 6,
        },
      }
    }

I found an answer from this question (need help in getting nested ruby hash hierarchy) that was helpful but even with some alterations it isn't working how I would like it:

def hashkeys(json, keys = [], result = [])
      if json.is_a?(Hash)
        json.each do |key, value|
          hashkeys(value, keys + [key], result)
        end
      else
        result << keys
      end
      result.join("/")
    end

It returns all the keys together as one string and doesn't include any of the respective values correctly as I would like.

Unwanted output of hashkeys currently:

id/system_name/mem/stat1/mem/stat2/...

Ideally I want something that takes in my_json:

find_nested_key_value(my_json)
   some logic loop involving key and value:
      if more logic needed
         another_logic_loop_for_more_nested_info
      else
         send_info(final_key, final_value)
      end
   end
 end

So if final_key = "mem/stat1" then the final_value = 10, then the next iteration would be final_key = "mem/stat2" and final_value = 1056 and so on

How do I achieve this? and is using a function like hashkeys the best way to achieve this


Solution

  • This is a recursive method that will create a "flattened hash", a hash without nesting and where the keys are the nested keys separated by slashes.

     def flatten_hash(hash, result = {}, prefix = nil)
       hash.each do |k,v|
         if v.is_a? Hash
           flatten_hash(v, result, [prefix, k].compact.join('/'))
         else
           result[[prefix, k].compact.join('/')] = v
         end
       end
       result
     end
    
    my_hash = {'id': 1, 'system_name': 'Sysem_1', 'mem': {'stat1': 10, 'stat2': 1056, 'stat3': 10563}}
    
    flatten_hash(my_hash)
    => {"id"=>1, "system_name"=>"Sysem_1", "mem/stat1"=>10, "mem/stat2"=>1056, "mem/stat3"=>10563}