4

I have a a few parameters that I am trying to build a deep-nested json object from ie: company.address.city company.address.state

Here are my params:

{"business_type"=>"company", "company.address.city"=>"Gold Coast", "company.address.line1"=>"123 fake street", "company.address.state"=>"QLD", "company.name"=>"test"}

I'm expecting something like:

business_type: "company",
company{
  address{
     city: "Gold Coast",
     line1: "123 fake street",
     state: "QLD",
  },
name: "test"
}
CodeCabin
  • 233
  • 1
  • 9

3 Answers3

3

In the form of a method in case you need it

h = {"business_type"=>"company", "company.address.city"=>"Gold Coast", "company.address.line1"=>"123 fake street", "company.address.state"=>"QLD", "company.name"=>"test"}

def flatten_keys(hash)
  hash.each_with_object({}) do |(key,value), all|
    parts = key.split('.').map!(&:to_sym)
    new = parts[0...-1].inject(all) { |h, k| h[k] ||= {} }
    new[parts.last] = value
  end
end

flatten_keys(h)

This printed out

=> {:business_type=>"company", :company=>{:address=>{:city=>"Gold Coast", :line1=>"123 fake street", :state=>"QLD"}, :name=>"test"}}

Hope this helps

kkp
  • 436
  • 4
  • 11
1

This is not the most elegant, but it works:

input = {"business_type"=>"company", "company.address.city"=>"Gold Coast", "company.address.line1"=>"123 fake street", "company.address.state"=>"QLD", "company.name"=>"test"}

res = input.reduce({}) do |memo, (keys_str, val)|
  keys = keys_str.split(".")
  last_key = keys[-1]
  hsh = memo
  keys[0...-1].each do |key|
    hsh[key] ||= {}
    hsh = hsh[key]
  end
  hsh[last_key] = val
  memo
end

puts res

which prints:

{"business_type"=>"company", "company"=>{"address"=>{"city"=>"Gold Coast", "line1"=>"123 fake street", "state"=>"QLD"}, "name"=>"test"}}

max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • Nailed it.. works perfectly - However so did kkp's answer and I'm selecting that as the answer purely because I understand it. But for anyone else needing an answer, this works well. – CodeCabin Jul 22 '19 at 02:20
1

Pure Ruby: you could define a custom method for deep assignement, for example like this inspired by https://stackoverflow.com/a/54122742:

def nested_set(h, keys, value)
  # keys = keys.map(&:to_sym)
  last_key = keys.pop
  position = h
  keys.each do |key|
    position[key] = {} unless position[key].is_a? Hash
    position = position[key]
  end
  position[last_key] = value
end

Then, given the parameters as data input it's easy to call it whenever you need it:

parameters.each.with_object({}) { |(k, v), res| nested_set(res, k.split('.'), v) }

#=> {"business_type"=>"company", "company"=>{"address"=>{"city"=>"Gold Coast", "line1"=>"123 fake street", "state"=>"QLD"}, "name"=>"test"}}


Or define a method to be more handy
def do_that_on parameters
  parameters.each.with_object({}) { |(k, v), res| nested_set(res, k.split('.'), v) }
end
iGian
  • 11,023
  • 3
  • 21
  • 36