ChatGPT解决这个技术问题 Extra ChatGPT

How to replace a hash key with another key

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

+1 for useful question
@a5his : I'm glad it was of help :)

g
gayavat
hash[:new_key] = hash.delete :old_key

Saved me a couple LOC, love it!
I often don't like "smart" ruby code because it takes some time to tell what it is really doing. Your solution is in other hand simple and descriptive.
This should indeed be the accepted answer! Easy, clean and straight to the point!
This answer is elegant but it doesn't actually answer the question. The post states that the keys which need replacing are unknown... We only know that they begin with an underscore, we don't know what the keys actually are.
so this creates a new key/value pair where you specify the new key and get the value from what hash.delete :old_key returns and the delete uses the old key. WOW, I want it tattooed somewhere :-D Thanks
g
gayavat

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


It's Rails method, not standard. Good answer though.
Also, this method can't operate with hash keys recursively.
deep_transform_keys can be used for it :) apidock.com/rails/v4.2.1/Hash/deep_transform_keys
At last! This is exactly what I searched!
This is a standard part of the language as of Ruby 2.5: docs.ruby-lang.org/en/trunk/Hash.html#method-i-transform_keys
m
mu is too short

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.


yours answer worked but after ward i found few hash like this
{"_id"=>"4de7140772f8be03da000018", "_type"=>"WorkStation", "created_at"=>"2011-06-02T10:24:35+05:45", "input_header_ids"=>[], "line_id"=>"4de7140472f8be03da000017", "updated_at"=>"2011-06-02T10:24:35+05:45"}
{"id"=>"4de7140772f8be03da000018", "type"=>"WorkStation", "reated_at"=>"2011-06-02T10:24:35+05:45", "nput_header_ids"=>[], "ine_id"=>"4de7140472f8be03da000017", "pdated_at"=>"2011-06-02T10:24:35+05:45"}
@Manish: I did say "If all the keys are strings and all of them have the underscore prefix". I included an example approach for your "keys without underscore prefixes" in an update.
@Manish: "k" is for "key", "v" is for "value", "x" is for "I don't know what to call it but I was trained as a mathematician so I call it x".
S
Sadiksha Gautam

you can do

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

This should work for your case!


S
Swapnil Chincholkar

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')


How is your answer is then solution for my problem? If you thought this was helpful, then you could have added in the comment under the question. My question wasn't to replace a key with another key, the solution you gave is very basic Hash property. in my case it's not : hash[:new_key] = has[:old_key], instead it's : hash[:dynamic_key] = hash[:_dynamic_key], it was clear question on regex and not simple hash replace.
I came to this via a google search and wanted @Swapnil's answer. Thanks
D
DigitalRoss
h.inject({}) { |m, (k,v)| m[k.sub(/^_/,'')] = v; m }

I like that you tried to use a regex to filter out the underscores properly, but you should be aware that in ruby, unlike javascript and others, /^/ means 'start of string OR LINE', and /$/ means 'end of string OR LINE'. It's unlikely that the keys have newlines in them in this case, but you should be aware that using those two operators in ruby is not only error prone but also very dangerous when used wrong in validations against injections. See here for an explanation. Hope you don't mind spreading the awareness.
Y
Yurii Verbytskyi

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"}

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

C
CTS_AE

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"}
#   ]
# }


p
purushothaman poovai

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.


R
Repolês

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


s
stcho

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