将字符串转换为哈希中的符号的最佳方法


250

在Ruby中将哈希中的所有键从字符串转换为符号的(最快/最干净/直接)方式是什么?

解析YAML时,这将很方便。

my_hash = YAML.load_file('yml')

我希望能够使用:

my_hash[:key] 

而不是:

my_hash['key']


80
hash.symbolize_keyshash.deep_symbolize_keys如果您使用的是Rails ,请执行此操作。
Zaz 2014年

乔什(Josh),如果您将您的评论写成答案,我会投票赞成您。require'rails'; hash.deep_symbolize_keys在irb或pry中效果很好。:D
道格拉斯·艾伦

Answers:


239

Ruby> = 2.5docs)中,您可以使用:

my_hash.transform_keys(&:to_sym)

使用旧的Ruby版本?这是一种单行代码,它将散列复制到带有符号键的新散列中:

my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}

使用Rails可以使用:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 

5
啊,抱歉,不清楚-注入不会修改调用方。您需要执行my_hash = my_hash.inject({}){| memo,(k,v)| memo [k.to_sym] = v; 备忘录}
莎拉·梅

4
那正是我想要的。我对其进行了一些修改,并添加了一些行,甚至在嵌套的哈希中创建符号。如果您有兴趣的话,请看这里:any-where.de/blog/ruby-hash-convert-string-keys-to-symbols
Matt

37
在Ruby 1.9中,您可以这样使用each_with_objectmy_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
sgtFloyd 2011年

8
这不能处理递归哈希...一次性查找,但不查找DRY。
baash05

8
@BryanM。我进入讨论很晚了:-),但是您也可以使用该.tap方法消除最后通过的需要memo。我创建了所有解决方案(以及递归解决方案)的清理版本gist.github.com/Integralist/9503099
Integralist

307

如果您使用的是Rails,这是一种更好的方法:

参数。symbolize_keys

结束。

如果不是,只需撕掉他们的代码(它也在链接中):

myhash.keys.each do |key|
  myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end


45
不会象征嵌套的哈希。
oma

3
我将链接切换为symbolize_keys新的且有效的(Rails 3)URL。我最初只是为修复了URL to_options,但是该链接的文档为零。symbolize_keys实际上有一个描述,所以我改用它。
Craig Walker

23
deep_symbolize_keys!。在轨道2+上工作
mastaBlasta

14
对于那些好奇如何进行反向操作的人,hash.stringify_keys可以使用。
尼克,

112

对于Ruby中YAML的特定情况,如果键以' :' 开头,则它们将被自动插入为符号。

需要“ yaml”
需要'pp'
yaml_str =“
连接:
  -主机:host1.example.com
    港口:10000
  -主机:host2.example.com
    端口:20000
”
yaml_sym =“
:连接:
  -:host:host1.example.com
    :端口:10000
  -:host:host2.example.com
    :端口:20000
”
pp yaml_str = YAML.load(yaml_str)
放置yaml_str.keys.first.class
pp yaml_sym = YAML.load(yaml_sym)
放置yaml_sym.keys.first.class

输出:

#/opt/ruby-1.8.6-p287/bin/ruby〜/ test.rb
{“连接数” =>
  [{“ port” => 10000,“ host” =>“ host1.example.com”},
   {“端口” => 20000,“主机” =>“ host2.example.com”}]}
串
{:connections =>
  [{:port => 10000,:host =>“ host1.example.com”},
   {:port => 20000,:host =>“ host2.example.com”}]}
符号

15
甜!有没有一种方法可以将YAML#load_file所有键默认设置为符号,而不是将每个键都不必以冒号开头的字符串?
ma11hew28 2011年


60

如果您使用的是Rails,则更加简单-您可以使用HashWithIndifferentAccess并以String和Symbols形式访问键:

my_hash.with_indifferent_access 

也可以看看:

http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html


或者,您可以使用很棒的“ Ruby Facets” Gem,它包含了Ruby Core和Standard Library类的许多扩展。

  require 'facets'
  > {'some' => 'thing', 'foo' => 'bar'}.symbolize_keys
    =>  {:some=>"thing", :foo=>"bar}

另请参见:http : //rubyworks.github.io/rubyfaux/?doc=http : //rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash


2
实际上,情况恰恰相反。它从符号转换为字符串。要转换为符号,请使用my_hash.symbolize_keys
Espen

#symbolize_keys仅适用于Rails-不适用于纯Ruby / irb。另请注意,#symbolize_keys不适用于深度嵌套的哈希。
蒂罗



26

这是一种深度象征对象的方法

def symbolize(obj)
    return obj.inject({}){|memo,(k,v)| memo[k.to_sym] =  symbolize(v); memo} if obj.is_a? Hash
    return obj.inject([]){|memo,v    | memo           << symbolize(v); memo} if obj.is_a? Array
    return obj
end

不错,即使我将其重命名为deep_symbolize,我也将继续使用它:)
PierrOz 2014年


19

如果您使用的是json,并且想将其用作哈希,则可以在Ruby核心中进行:

json_obj = JSON.parse(json_str, symbolize_names: true)

symbolize_names:如果设置为true,则返回JSON对象中名称(键)的符号。否则返回字符串。字符串是默认值。

Doc:Json#parse symbolize_names


symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)这是一种非常干燥(但效率低下)的方法,可以从YAML文件中快速获取带有嵌套键作为符号的哈希。
卡梅隆·加格农


10

对@igorsales答案的修改

class Object
  def deep_symbolize_keys
    return self.inject({}){|memo,(k,v)| memo[k.to_sym] = v.deep_symbolize_keys; memo} if self.is_a? Hash
    return self.inject([]){|memo,v    | memo           << v.deep_symbolize_keys; memo} if self.is_a? Array
    return self
  end
end

如果您包括为什么要修改对象以供人们浏览答案,这将很有帮助。
Dbz

9

在Rails中,您可以使用:

{'g'=> 'a', 2 => {'v' => 'b', 'x' => { 'z' => 'c'}}}.deep_symbolize_keys!

转换为:

{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}

3
deep_symbolize_keys是在Rails的Hash扩展中添加的,但它不是Ruby核心的一部分。
Ryan Crispin Heneise


7

这是我的一个嵌套哈希的衬里

def symbolize_keys(hash)
  hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end

仅供参考,仅适用于Rails。在这种情况下,HashWithIndifferentAccess可能是更好的选择。
亚当·格兰特

6

万一您需要执行此操作的原因是因为您的数据最初来自JSON,则可以通过在:symbolize_names导入JSON时仅传递选项来跳过任何解析。

不需要Rails,并且可以与Ruby> 1.9一起使用

JSON.parse(my_json, :symbolize_names => true)

4

您可能会很懒,将其包装在lambda

my_hash = YAML.load_file('yml')
my_lamb = lambda { |key| my_hash[key.to_s] }

my_lamb[:a] == my_hash['a'] #=> true

但这仅适用于从哈希读取-而不是写入。

为此,您可以使用 Hash#merge

my_hash = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(YAML.load_file('yml'))

init块将按需一次转换键,但是如果在访问符号版本后更新键的字符串版本的值,则不会更新符号版本。

irb> x = { 'a' => 1, 'b' => 2 }
#=> {"a"=>1, "b"=>2}
irb> y = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(x)
#=> {"a"=>1, "b"=>2}
irb> y[:a]  # the key :a doesn't exist for y, so the init block is called
#=> 1
irb> y
#=> {"a"=>1, :a=>1, "b"=>2}
irb> y[:a]  # the key :a now exists for y, so the init block is isn't called
#=> 1
irb> y['a'] = 3
#=> 3
irb> y
#=> {"a"=>3, :a=>1, "b"=>2}

您还可以使init块不更新哈希,这样可以保护您免受此类错误的影响,但是您仍然容易受到相反的影响-更新符号版本不会更新字符串版本:

irb> q = { 'c' => 4, 'd' => 5 }
#=> {"c"=>4, "d"=>5}
irb> r = Hash.new { |h,k| h[k.to_s] }.merge(q)
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called
#=> 4
irb> r
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called again, since this key still isn't in r
#=> 4
irb> r[:c] = 7
#=> 7
irb> r
#=> {:c=>7, "c"=>4, "d"=>5}

因此,要注意的是在这两种键形式之间切换。坚持一个。


3

会进行以下工作吗?

new_hash = Hash.new
my_hash.each { |k, v| new_hash[k.to_sym] = v }

它会复制哈希,但是您大部分时间都不会在意。可能有一种无需复制所有数据的方法。


3

较短的单线运行:

my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }


2

这是针对使用mrubysymbolize_keys未定义任何方法的人的:

class Hash
  def symbolize_keys!
    self.keys.each do |k|
      if self[k].is_a? Hash
        self[k].symbolize_keys!
      end
      if k.is_a? String
        raise RuntimeError, "Symbolizing key '#{k}' means overwrite some data (key :#{k} exists)" if self[k.to_sym]
        self[k.to_sym] = self[k]
        self.delete(k)
      end
    end
    return self
  end
end

方法:

  • 仅象征着 String
  • 如果符号字符串表示丢失某些信息(覆盖哈希的一部分),则引发 RuntimeError
  • 还象征性地递归包含哈希
  • 返回符号哈希
  • 工作到位!

1
有一个在你的方法一个错字,你忘了!symbolize_keys。否则工作正常。
嘉莫

1

我们要更改的数组。

字符串= [“ HTML”,“ CSS”,“ JavaScript”,“ Python”,“ Ruby”]

将新变量设置为空数组,以便我们可以“ .push”符号。

符号= []

这是我们定义带有块的方法的地方。

strings.each {| x | symbol.push(x.intern)}

代码结尾。

因此,这可能是在Ruby中将字符串转换为数组中的符号的最直接的方法。创建一个字符串数组,然后创建一个新变量,并将该变量设置为一个空数组。然后在使用“ .each”方法创建的第一个数组中选择每个元素。然后,使用块代码“压入”新数组中的所有元素,并使用“ .intern或.to_sym”将所有元素转换为符号。

符号之所以更快,是因为它们可以节省代码中的更多内存,并且您只能使用一次。符号最常用于哈希中的键,这很棒。我不是最好的ruby程序员,但是这种形式的代码对我有很大帮助。如果有人知道更好的方法,请分享,您也可以使用此方法进行哈希!


2
问题是关于散列,而不是数组。
Richard_G '16

1

如果您想要香草红宝石解决方案,而由于我没有访问权限,ActiveSupport这里是深层象征解决方案(与以前的解决方案非常相似)

    def deep_convert(element)
      return element.collect { |e| deep_convert(e) } if element.is_a?(Array)
      return element.inject({}) { |sh,(k,v)| sh[k.to_sym] = deep_convert(v); sh } if element.is_a?(Hash)
      element
    end

1

Psych 3.0 开始,您可以添加symbolize_names:选项

Psych.load("---\n foo: bar") # => {"foo"=>"bar"}

Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}

注意:如果您的Psych版本低于3.0,symbolize_names:将被忽略。

我的Ubuntu 18.04随同ruby 2.5.1p57一起提供


0
ruby-1.9.2-p180 :001 > h = {'aaa' => 1, 'bbb' => 2}
 => {"aaa"=>1, "bbb"=>2} 
ruby-1.9.2-p180 :002 > Hash[h.map{|a| [a.first.to_sym, a.last]}]
 => {:aaa=>1, :bbb=>2}

您可以将a方括号括起来以分解block参数,以使其更加简洁。例如,请参阅我的答案。
Michael Barton 2012年

0

这不完全是一种单行代码,但是它会将所有字符串键转换为符号,也将嵌套的符号转换为符号:

def recursive_symbolize_keys(my_hash)
  case my_hash
  when Hash
    Hash[
      my_hash.map do |key, value|
        [ key.respond_to?(:to_sym) ? key.to_sym : key, recursive_symbolize_keys(value) ]
      end
    ]
  when Enumerable
    my_hash.map { |value| recursive_symbolize_keys(value) }
  else
    my_hash
  end
end

0

当我不使用Rails时,我喜欢这种单行代码,因为那样的话,我在处理它时就不必进行第二次哈希处理并保存两组数据:

my_hash = { "a" => 1, "b" => "string", "c" => true }

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key) }

my_hash
=> {:a=>1, :b=>"string", :c=>true}

Hash#delete返回已删除键的值


0

Facets的Hash#deep_rekey也是一个不错的选择,尤其是:

  • 如果您从项目的各个方面发现其他糖的用途,
  • 如果您更喜欢代码可读性而不是神秘的一线代码。

样品:

require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey

0

在ruby中,我发现这是将哈希中的字符串键转换为符号的最简单易懂的方法:

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key)}

对于哈希中的每个键,我们在其上调用delete,以将其从哈希中删除(删除也返回与已删除键相关联的值),我们立即将其设置为符号键。


0

与先前的解决方案相似,但写法有所不同。

  • 这允许嵌套和/或具有数组的哈希。
  • 获得将键转换为字符串的奖励。
  • 代码不会使传入的哈希值发生变化。

    module HashUtils
      def symbolize_keys(hash)
        transformer_function = ->(key) { key.to_sym }
        transform_keys(hash, transformer_function)
      end
    
      def stringify_keys(hash)
        transformer_function = ->(key) { key.to_s }
        transform_keys(hash, transformer_function)
      end
    
      def transform_keys(obj, transformer_function)
        case obj
        when Array
          obj.map{|value| transform_keys(value, transformer_function)}
        when Hash
          obj.each_with_object({}) do |(key, value), hash|
            hash[transformer_function.call(key)] = transform_keys(value, transformer_function)
          end
        else
          obj
        end
      end
    end
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.