Home > database >  Return an ordered JSONB nested hash
Return an ordered JSONB nested hash

Time:10-13

I've a nested JSONB hash that I require to display in date order.

The hash is stored like so:

hash = 
{"residential_la"=>
  {"current"=>{"periods"=>{"1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0", "7/2023"=>"0", "8/2023"=>"0", "9/2023"=>"0", "10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0"}},
   "original"=>{"periods"=>{"1/2023"=>"901", "2/2023"=>"1315", "3/2023"=>"4377", "4/2023"=>"1815", "5/2023"=>"1835", "6/2023"=>"896", "7/2023"=>"1996", "8/2023"=>"4219", "9/2023"=>"3369", "10/2022"=>"3335", "11/2022"=>"4198", "12/2022"=>"3127"}},
   "NominalCode"=>"500"},
 "residential_private"=>
  {"current"=>{"periods"=>{"1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0", "7/2023"=>"0", "8/2023"=>"0", "9/2023"=>"0", "10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0"}},
   "original"=>{"periods"=>{"1/2023"=>"4389", "2/2023"=>"1265", "3/2023"=>"4496", "4/2023"=>"980", "5/2023"=>"1617", "6/2023"=>"1396", "7/2023"=>"4839", "8/2023"=>"4248", "9/2023"=>"1770", "10/2022"=>"3513", "11/2022"=>"1294", "12/2022"=>"4240"}},
   "NominalCode"=>"520"}}

As you can see, being JSONB it's storing it in length order. I need to sort this when calling to display in the correct way.

I know for sure I can sort the periods nested hash with the following:

{ periods: periods_hash[:periods].sort_by { |k, _v| Date.strptime(k, "%d/%Y") }.to_h }

This creates a new hash of periods and sorts it, correcty.

My problem is getting to this nested hash and returning the full hash with no changes but the order of those periods.

My current efforts produced this:

def order_data
    data.each do |_k, v|
      v.each do |sk, sv|
        next if sk.include? "NominalCode"

        sv.each do |x, _y|
         { periods: x[:periods].sort_by { |k, _v| Date.strptime(k, "%d/%Y") }.to_h }
        end
      end
    end
  end

The above isn't working and gives me no implicit conversion of Symbol into Integer, I'm assuiming as I'm .each'ing there's a problem with enummerable and the symbols of the hash.

What I'm expecting in return is:

{"residential_la"=>
  {"current"=>{"periods"=>{"10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0", "7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0"}},
   "original"=>{"periods"=>{"10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0", "7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0"}},
   "NominalCode"=>"500"},
"residential_private"=>
  {"current"=>{"periods"=>{"10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0", "7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0"}},
   "original"=>{"periods"=>{"10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0", "7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0"}},
   "NominalCode"=>"500"}

I'm unsure whether I should be mapping to create a new hash, or just iterating through with each to only modify the sub hash, ultimately there's a lack of understanding on my part, any help and guidance is appreciated.

Thanks.

CodePudding user response:

Given the following task definition:

  • we need to sort any hash under key periods...
  • regardless of its (periods) depth
  • and keep everything else intact

one could go with the simple recursive algorithm (let's name it deep_sort_periods):

  1. Start with a new hash (n)
  2. Check the next key/value (k/v) of the existing hash
  3. If the v is not a hash leave it as is (set n[k] = v)
  4. If the v is a hash and k is periods - sort the value (n[k] = <your sorting logic>)
  5. Otherwise (v is a hash but k is smth. different, not periods) - repeat 1-4 for the nested hash and assign the result to n[k] (n[k] = deep_sort_periods(v))

The implementation can literally follow the description, kinda:

def deep_sort_periods(hash)
  hash.each_with_object({}) do |(k, v), new_hash|
    if !v.is_a?(Hash)
      new_hash[k] = v
    elsif k == "periods"
      new_hash[k] = v.sort_by { |k, _v| Date.strptime(k, "%d/%Y") }.to_h
    else
      new_hash[k] = deep_sort_periods(v)
    end
  end
end

and then

pry(main)> deep_sort_periods(hash)
=> {"residential_la"=>
  {"current"=>{"periods"=>{"7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0", "10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0"}},
   "original"=>{"periods"=>{"7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0", "10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0"}},
   "NominalCode"=>"500"},
 "residential_private"=>
  {"current"=>{"periods"=>{"7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0", "10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0"}},
   "original"=>{"periods"=>{"7/2022"=>"0", "8/2022"=>"0", "9/2022"=>"0", "10/2022"=>"0", "11/2022"=>"0", "12/2022"=>"0", "1/2023"=>"0", "2/2023"=>"0", "3/2023"=>"0", "4/2023"=>"0", "5/2023"=>"0", "6/2023"=>"0"}},
   "NominalCode"=>"500"}}

Disclaimer: if there are more restrictions for the task definition - for example, periods are not arbitrarily nested but rather sit at some fixed depth - the task most probably can be solved in a more concise and performant way with no recursion at all...

  • Related