How to replace a hash key with another key

Ruby on-RailsRubyRuby on-Rails-3Hash

Ruby on-Rails Problem Overview


I have a condition where, I get a hash

  hash = {"_id"=>"4de7140772f8be03da000018", .....}

and I want this hash as

  hash = {"id"=>"4de7140772f8be03da000018", ......}

P.S: I don't know what are the keys in the hash, they are random which comes with an "_" prefix for every key and I want no underscores

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

hash[:new_key] = hash.delete :old_key

Solution 2 - Ruby on-Rails

rails Hash has standard method for it:

hash.transform_keys{ |key| key.to_s.upcase }

http://api.rubyonrails.org/classes/Hash.html#method-i-transform_keys

UPD: ruby 2.5 method

Solution 3 - Ruby on-Rails

If all the keys are strings and all of them have the underscore prefix, then you can patch up the hash in place with this:

h.keys.each { |k| h[k[1, k.length - 1]] = h[k]; h.delete(k) }

The k[1, k.length - 1] bit grabs all of k except the first character. If you want a copy, then:

new_h = Hash[h.map { |k, v| [k[1, k.length - 1], v] }]

Or

new_h = h.inject({ }) { |x, (k,v)| x[k[1, k.length - 1]] = v; x }

You could also use sub if you don't like the k[] notation for extracting a substring:

h.keys.each { |k| h[k.sub(/\A_/, '')] = h[k]; h.delete(k) }
Hash[h.map { |k, v| [k.sub(/\A_/, ''), v] }]
h.inject({ }) { |x, (k,v)| x[k.sub(/\A_/, '')] = v; x }

And, if only some of the keys have the underscore prefix:

h.keys.each do |k|
  if(k[0,1] == '_')
    h[k[1, k.length - 1]] = h[k]
    h.delete(k)
  end
end

Similar modifications can be done to all the other variants above but these two:

Hash[h.map { |k, v| [k.sub(/\A_/, ''), v] }]
h.inject({ }) { |x, (k,v)| x[k.sub(/\A_/, '')] = v; x }

should be okay with keys that don't have underscore prefixes without extra modifications.

Solution 4 - Ruby on-Rails

you can do

hash.inject({}){|option, (k,v) | option["id"] = v if k == "_id"; option}

This should work for your case!

Solution 5 - Ruby on-Rails

If we want to rename a specific key in hash then we can do it as follows:
Suppose my hash is my_hash = {'test' => 'ruby hash demo'}
Now I want to replace 'test' by 'message', then:
my_hash['message'] = my_hash.delete('test')

Solution 6 - Ruby on-Rails

h.inject({}) { |m, (k,v)| m[k.sub(/^_/,'')] = v; m }

Solution 7 - Ruby on-Rails

For Ruby 2.5 or newer with transform_keys and delete_prefix / delete_suffix methods:

hash1 = { '_id' => 'random1' }
hash2 = { 'old_first' => '123456', 'old_second' => '234567' }
hash3 = { 'first_com' => 'google.com', 'second_com' => 'amazon.com' }

hash1.transform_keys { |key| key.delete_prefix('_') }
# => {"id"=>"random1"}
hash2.transform_keys { |key| key.delete_prefix('old_') }
# => {"first"=>"123456", "second"=>"234567"}
hash3.transform_keys { |key| key.delete_suffix('_com') }
# => {"first"=>"google.com", "second"=>"amazon.com"}

Solution 8 - Ruby on-Rails

hash.each {|k,v| hash.delete(k) && hash[k[1..-1]]=v if k[0,1] == '_'}

Solution 9 - Ruby on-Rails

I went overkill and came up with the following. My motivation behind this was to append to hash keys to avoid scope conflicts when merging together/flattening hashes.

Examples

Extend Hash Class

Adds rekey method to Hash instances.

# Adds additional methods to Hash
class ::Hash
  # Changes the keys on a hash
  # Takes a block that passes the current key
  # Whatever the block returns becomes the new key
  # If a hash is returned for the key it will merge the current hash 
  # with the returned hash from the block. This allows for nested rekeying.
  def rekey
    self.each_with_object({}) do |(key, value), previous|
      new_key = yield(key, value)
      if new_key.is_a?(Hash)
        previous.merge!(new_key)
      else
        previous[new_key] = value
      end
    end
  end
end
Prepend Example
my_feelings_about_icecreams = {
  vanilla: 'Delicious',
  chocolate: 'Too Chocolatey',
  strawberry: 'It Is Alright...'
}

my_feelings_about_icecreams.rekey { |key| "#{key}_icecream".to_sym }
# => {:vanilla_icecream=>"Delicious", :chocolate_icecream=>"Too Chocolatey", :strawberry_icecream=>"It Is Alright..."}
Trim Example
{ _id: 1, ___something_: 'what?!' }.rekey do |key|
  trimmed = key.to_s.tr('_', '')
  trimmed.to_sym
end
# => {:id=>1, :something=>"what?!"}
Flattening and Appending a "Scope"

If you pass a hash back to rekey it will merge the hash which allows you to flatten collections. This allows us to add scope to our keys when flattening a hash to avoid overwriting a key upon merging.

people = {
  bob: {
    name: 'Bob',
    toys: [
      { what: 'car', color: 'red' },
      { what: 'ball', color: 'blue' }
    ]
  },
  tom: {
    name: 'Tom',
    toys: [
      { what: 'house', color: 'blue; da ba dee da ba die' },
      { what: 'nerf gun', color: 'metallic' }
    ]
  }
}

people.rekey do |person, person_info|
  person_info.rekey do |key|
    "#{person}_#{key}".to_sym
  end
end

# =>
# {
#   :bob_name=>"Bob",
#   :bob_toys=>[
#     {:what=>"car", :color=>"red"},
#     {:what=>"ball", :color=>"blue"}
#   ],
#   :tom_name=>"Tom",
#   :tom_toys=>[
#     {:what=>"house", :color=>"blue; da ba dee da ba die"},
#     {:what=>"nerf gun", :color=>"metallic"}
#   ]
# }

Solution 10 - Ruby on-Rails

Previous answers are good enough, but they might update original data. In case if you don't want the original data to be affected, you can try my code.

 newhash=hash.reject{|k| k=='_id'}.merge({id:hash['_id']})

First it will ignore the key '_id' then merge with the updated one.

Solution 11 - Ruby on-Rails

Answering exactly what was asked:

hash = {"_id"=>"4de7140772f8be03da000018"}
hash.transform_keys { |key| key[1..] }
# => {"id"=>"4de7140772f8be03da000018"}

The method transform_keys exists in the Hash class since Ruby version 2.5.

https://blog.bigbinary.com/2018/01/09/ruby-2-5-adds-hash-transform_keys-method.html

Solution 12 - Ruby on-Rails

If you had a hash inside a hash, something like

hash = {
  "object" => {
     "_id"=>"4de7140772f8be03da000018"
  }
}

and if you wanted to change "_id" to something like"token"

you can use deep_transform_keys here and do it like so

hash.deep_transform_keys do |key|
  key = "token" if key == "_id"
  key
end

which results in

{
  "object" => {
     "token"=>"4de7140772f8be03da000018"
  }
}

Even if you had a symbol key hash instead to start with, something like

hash = {
  object: {
     id: "4de7140772f8be03da000018"
  }
}

you can combine all of these concepts to convert them into a string key hash

hash.deep_transform_keys do |key|
  key = "token" if key == :id
  key.to_s
end

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionManish DasView Question on Stackoverflow
Solution 1 - Ruby on-RailsgayavatView Answer on Stackoverflow
Solution 2 - Ruby on-RailsgayavatView Answer on Stackoverflow
Solution 3 - Ruby on-Railsmu is too shortView Answer on Stackoverflow
Solution 4 - Ruby on-RailsSadiksha GautamView Answer on Stackoverflow
Solution 5 - Ruby on-RailsSwapnil ChincholkarView Answer on Stackoverflow
Solution 6 - Ruby on-RailsDigitalRossView Answer on Stackoverflow
Solution 7 - Ruby on-RailsYurii VerbytskyiView Answer on Stackoverflow
Solution 8 - Ruby on-RailsmaericsView Answer on Stackoverflow
Solution 9 - Ruby on-RailsCTS_AEView Answer on Stackoverflow
Solution 10 - Ruby on-Railspurushothaman poovaiView Answer on Stackoverflow
Solution 11 - Ruby on-RailsRepolêsView Answer on Stackoverflow
Solution 12 - Ruby on-RailsstchoView Answer on Stackoverflow