7

给定一个散列,其中包含一个嵌套散列:

hash = {"some_key" => "value",
        "nested" => {"key1" => "val1", 
                     "key2" => "val2"}}

以及字符串格式的键的路径:

path = "nested.key2"

如何在 key2 条目之前添加新的键值对?所以,预期的输出应该是这样的:

hash = {"some_key" => "value",
        "nested" => {"key1" => "val1",
                    "new_key" => "new_value"},
                    "key2" => "val2"}}

已编辑

我的目标是在某个键之前添加一种标签,以便将哈希转储为 Yaml 文本并后处理文本以用 Yaml 注释替换添加的键/值。AFAIK,没有其他方法可以以编程方式在 YAML 中的特定键之前添加注释。

4

4 回答 4

7

通过使用哈希的数组表示,这是最简单的:

subhash   = hash['nested'].to_a
insert_at = subhash.index(subhash.assoc('key2'))
hash['nested'] = Hash[subhash.insert(insert_at, ['new_key', 'new_value'])]

它可以包装成一个函数:

class Hash
  def insert_before(key, kvpair)
    arr = to_a
    pos = arr.index(arr.assoc(key))
    if pos
      arr.insert(pos, kvpair)
    else
      arr << kvpair
    end
    replace Hash[arr]
  end
end

hash['nested'].insert_before('key2', ['new_key', 'new_value'])

p hash # {"some_key"=>"value", "nested"=>{"key1"=>"val1", "new_key"=>"new_value", "key2"=>"val2"}}
于 2013-06-22T13:44:37.203 回答
2

我经常为应用程序的大型配置创建 YAML 生成器。为了维护,我需要对字段进行排序。

我在按排序顺序生成 YAML 时使用的解决方案是根据需要添加键以将它们放入正确的散列或子散列中。然后我通过对键/值对进行排序来创建一个新的哈希并to_yaml对其进行操作。

对哈希进行排序是没有意义的,但是在让 YAML 让它工作之前对要输出的临时哈希进行排序并产生更容易维护的文件。

require 'yaml'

some_hash = {
    'z' => 1,
    'a' => 3
}

puts some_hash.to_yaml

哪个输出:

---
z: 1
a: 3

在创建 YAML 输出之前对其进行排序:

puts Hash[some_hash.merge('x' => 2).sort_by{ |k, v| k }].to_yaml

输出:

---
a: 3
x: 2
z: 1

而不是puts使用File.write或嵌入该行到传递给的块中File.open


关于 YAML 文件中的注释:YAML 不支持以编程方式向发出的输出添加注释。注释适用于人类,#不能映射到 Ruby 变量或对象。可以这样想:如果我们从一个名为的文件中的这个 YAML 开始test.yaml

---
# string
a: 'fish'
# array
b: 
  - 1
  - 2
# hash
c: 
  d: 'foo'
  e: 'bar'
# integer
z: 1

并加载它:

require 'pp'
require 'yaml'

obj = YAML.load_file('test.yaml')

pp obj

obj看起来像:

{"a"=>"fish", "b"=>[1, 2], "c"=>{"d"=>"foo", "e"=>"bar"}, "z"=>1}

没有返回“注释”对象,并且 Ruby 中不存在任何适合散列的对象,这些对象存在于 YAML 规范中。我们可以随意拼凑出一个我们称为 Comment 的类,并尝试将其作为键嵌入到对象中,但 YAML 不会接受它作为注释,因为规范不允许这样做。它会将其定义为 Ruby 类并将其重新创建为该类,但不会显示为#注释:

require 'yaml'

class Comment
  def initialize(some_text)
    @comment = "# #{some_text}"
  end
end

some_hash = {
  'a' => 1,
  Comment.new('foo') => 'bar',
  'z' => 'z'
}

puts some_hash.to_yaml

输出:

---
a: 1
? !ruby/object:Comment
  comment: ! '# foo'
: bar
z: z

当我在发出的 YAML 配置中需要注释时,我会手动调整它们以便稍后添加它们。对于您想要做的事情,而不是进行任何手动调整,我建议您使用可以在文档中扫描的更多助记符或唯一变量名称。您甚至可以放入除了充当占位符之外不提供任何有价值的东西的虚拟条目:

require 'yaml'

some_hash = {
  'a' => 1,
  '__we_are_here__' => '',
  'b' => 2,
  '__we_are_now_here__' => '',
  'z' => 'z'
}

puts some_hash.to_yaml

生成一个 YAML 文件,如:

---
a: 1
__we_are_here__: ''
b: 2
__we_are_now_here__: ''
z: z

至于将密钥插入哈希,我可能会稍微重组我的“密钥链”,以显示我想要插入它的路径以及新密钥的名称。同样,在保存 YAML 之前,我会依靠排序来确保事情的顺序正确:

require 'pp'

# this changes the incoming hash
def insert_embedded_hash_element(hash, key_path, new_value)

  keys = key_path.split('.')
  new_key = keys.pop

  sub_hash = hash
  keys.each do |k|
    sub_hash = sub_hash[k]
  end

  sub_hash[new_key] = new_value

end

# the sub-hash to insert into + new key name
insert_key = 'nested.key2'
insert_value = 'new_value'

hash = {
  "some_key" => "value",
  "nested" => {
    "key1" => "val1", 
    "key3" => "val2"
  }
}

insert_embedded_hash_element(hash, insert_key, insert_value)

pp hash

导致:

{"some_key"=>"value",
 "nested"=>{"key1"=>"val1", "key3"=>"val2", "key2"=>"new_value"}}
于 2013-06-21T14:43:33.953 回答
2

这只是根据OP的需要,但可以随时根据需要进行修改:

require 'yaml'

hash = {"some_key" => "value",
        "nested" => {"key1" => "val1", 
                     "key2" => "val2"}}

new_hash = %w(nested key2).inject(hash) do |h,i|
 next h[i] unless h.has_key? "key2"
 ind = h.to_a.index{|m| m[0] == i }
 Hash[h.to_a.insert(ind,["new_key","new_value"])]
end

hash["nested"] = new_hash # this part is to be taken care of for deep hash.
puts hash.to_yaml

输出:

some_key: value
nested:
  key1: val1
  new_key: new_value
  key2: val2

更新:

我找到了更高效的代码,这将减少hash["nested"] = new_hash我之前代码中处理该行的开销:

require 'yaml'

hash = {"some_key" => "value",
        "nested" => {"key1" => "val1", 
                     "key2" => "val2"}}

new_hash = %w(nested key2).inject(hash) do |h,i| # !> assigned but unused variable - new_hash
 next h[i] unless h.has_key? "key2"
 ind = h.to_a.index{|m| m[0] == i }
 h1 = Hash[h.to_a.insert(ind,["new_key","new_value"])]
 h.replace(h1)
end

hash
# => {"some_key"=>"value",
#     "nested"=>{"key1"=>"val1", "new_key"=>"new_value", "key2"=>"val2"}}

puts hash.to_yaml
# >> ---
# >> some_key: value
# >> nested:
# >>   key1: val1
# >>   new_key: new_value
# >>   key2: val2
于 2013-06-21T14:48:56.020 回答
0

我不认为 ruby​​ 是免费提供这个功能的。你可以做这样的事情,你正在创建一个现有哈希键的数组,将你的新键插入到数组中,然后用新排序的键创建一个新的哈希。

keys = original_hash.keys
keys.insert(new_key_position, new_key)

new_hash = {}
keys.each do |key|
  new_hash[key] = key == new_key ? new_value : original_hash[key]
end
于 2013-06-21T14:02:30.660 回答