11

Given hash with nested documents:

myHash = {
  "MemberId"=>"ABC0001", 
  "MemberName"=>"Alan", 
  "details"=>[
    {"LineNumber"=>"4.1", "Item"=>"A0001", "Description"=>"Apple"}, 
    {"LineNumber"=>"5.1", "Item"=>"A0002"}, 
    {"LineNumber"=>"6.1", "Item"=>"Orange"}
  ]
}

I want to change it so it will look like:

{
  "memberid"=>"ABC0001", 
  "membername"=>"Alan", 
  "details"=>[
    {"linenumber"=>"4.1", "item"=>"A0001", "description"=>"Apple"}, 
    {"linenumber"=>"5.1", "item"=>"A0002"}, 
    {"linenumber"=>"6.1", "item"=>"Orange"}
  ]
}

In other words, I want to change to lower case if any in the hash key. I understand I'll have to iterate through the hash and use downcase method. If there any easy way of doing this in ruby?

Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168
Askar
  • 5,784
  • 10
  • 53
  • 96

6 Answers6

16

You can simple do

hash.transform_keys(&:downcase)

to change hash keys to lowercase.

you can refer my answer https://stackoverflow.com/a/54090178/8247133

Touseef Murtaza
  • 1,548
  • 14
  • 20
5
class Hash
  def downcase_key
    keys.each do |k|
      store(k.downcase, Array === (v = delete(k)) ? v.map(&:downcase_key) : v)
    end
    self
  end
end

myHash.downcase_key
sawa
  • 165,429
  • 45
  • 277
  • 381
3
def f h
  Hash[h.map{|k,v| v.class == Array ? [k,v.map{|r| f r}.to_a] : [k.downcase,v]}]
end

proof

tkroman
  • 4,811
  • 1
  • 26
  • 46
3

I would first create a method on Hash that allows you to map the keys to new values:

class Hash
  def map_keys! &blk
    keys.each do |k|
      new_k = blk.call(k)
      self[new_k] = delete(k)
    end
    self
  end

  def map_keys &blk
    dup.map_keys!(&blk)
  end
end

You can now downcase the first level with

myHash.map_keys!(&:downcase)

myHash now contains:

{"details"=>
  [{"LineNumber"=>"4.1", "Item"=>"A0001", "Description"=>"Apple"},
   {"LineNumber"=>"5.1", "Item"=>"A0002"},
   {"LineNumber"=>"6.1", "Item"=>"Orange"}],
 "memberid"=>"ABC0001",
 "membername"=>"Alan"}

The nested hashes can be converted with

myHash['details'].each{|h| h.map_keys!(&:downcase) }

myHash now contains:

{"details"=>
  [{"linenumber"=>"4.1", "item"=>"A0001", "description"=>"Apple"},
   {"linenumber"=>"5.1", "item"=>"A0002"},
   {"linenumber"=>"6.1", "item"=>"Orange"}],
 "memberid"=>"ABC0001",
 "membername"=>"Alan"}
Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168
1

I found this solution while working on the same problem today.

def lowercase_keys(h)
  new_h = { }
  h.each_pair do |k, v|
    new_k = deep_lowercase(k, true)
    new_v = deep_lowercase(v, false)
    new_h[new_k] = new_v
  end
  new_h
end

def deep_lowercase(object, with_strings)
  case object
    when String then
      with_strings ? object.downcase : object
    when Hash then
      lowercase_keys(object)
    else
      object.respond_to?(:map) ? object.map { |e| deep_lowercase(e, false) } : object
  end
end

From my tests it works whether Hash are Values or Keys themselves or embedded in an Enumerable.

ZeDalaye
  • 689
  • 7
  • 17
0

Here's a function that recursively descends into any arbitrarily nested enumerables and downcases any hash keys that it finds:

data = {
  "MemberId"=>"ABC0001", 
  "MemberName"=>"Alan", 
  "details"=>[
    {"LineNumber"=>"4.1", "Item"=>"A0001", "Description"=>"Apple"}, 
    {"LineNumber"=>"5.1", "Item"=>"A0002"}, 
    {"LineNumber"=>"6.1", "Item"=>"Orange"}
  ]
}

def downcase_hash_keys(h)
  if h.is_a?(Hash)
    h.keys.each do |key|
      new_key = key.to_s.downcase
      h[new_key] = h.delete(key)
      downcase_hash_keys(h[new_key])
    end
  elsif h.respond_to?(:each)
    h.each { |e| downcase_hash_keys(e) }
  end
  h
end

downcase_hash_keys(data)
p data


# => {"memberid"=>"ABC0001", "membername"=>"Alan", "details"=>[{"linenumber"=>"4.1", "item"=>"A0001", "description"=>"Apple"}, {"linenumber"=>"5.1", "item"=>"A0002"}, {"linenumber"=>"6.1", "item"=>"Orange"}]}

It's a bit ugly because it mutates the data structure in place; ideally, a new hash would be created. But it achieves the desired result.

Catnapper
  • 1,875
  • 10
  • 12