在特定位置的Ruby哈希中插入条目

时间:2021-02-01 01:39:06

Given a hash with, say, a nested hash in it:

给定一个散列,例如,嵌套散列:

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

and a path to a key in String format:

和String格式的键的路径:

path = "nested.key2"

How can I add a new key-value pair before the key2 entry? So, expected output should be something like this:

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

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

EDITED

My goal is to add a kind of label before some key in order to dump hash as Yaml text and post process the text to replace added key/value with Yaml comment. AFAIK, there is no other way to add comment before specific key in YAML programmatically.

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

4 个解决方案

#1


7  

This is easiest by using the Hash's array representation:

使用Hash的数组表示最简单:

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

It could be wrapped into a function:

它可以包装成一个函数:

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

#2


2  

I often create YAML generators for large configurations for apps. For maintenance I need the fields sorted.

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

The solution I use when generating YAML in a sorted order, is to add the keys as necessary to get them in the right hash, or sub-hash. Then I create a new hash by sorting the key/value pairs and do to_yaml on it.

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

There's no point sorting a hash but sorting the temporary hash to be output prior to letting YAML have it does work and results in a more easily maintained file.

没有必要对散列进行排序,但在让YAML使用它之前对要输出的临时散列进行排序是有效的,并且会导致更容易维护的文件。

require 'yaml'

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

puts some_hash.to_yaml

Which outputs:

---
z: 1
a: 3

Sorting it prior to creating the YAML output:

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

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

Outputs:

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

Instead of puts, use File.write or embed the line in a block passed to File.open.

而不是put,使用File.write或将行嵌入传递给File.open的块中。


Regarding comments in YAML files: YAML doesn't support adding comments programmatically to the emitted output. Comments are for humans, and the # isn't mappable to a Ruby variable or object. Think of it this way: If we start with this YAML in a file called test.yaml:

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

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

And load it:

并加载它:

require 'pp'
require 'yaml'

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

pp obj

I get an obj looking like:

我得到一个obj看起来像:

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

There is no "comment" objects returned, and there are none that exist in Ruby that would fit into the hash, that exists in the YAML spec. We could arbitrarily cobble up a class that we call Comment, and try embedding it in the object as a key, but YAML wouldn't accept it as a comment, because the spec doesn't allow it. It would define it as a Ruby class and recreate it as that class, but it wouldn't be displayed as a # comment:

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

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

Outputting:

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

When I've needed comments in my emitted YAML configurations, I manually tweak them to add them later. For what you want to do, rather than do any manual tweaking, I'd recommend using more mnemonic or unique variable names that you can scan for in your document. You could even put in dummy entries that don't provide to anything worthwhile except acting as a place holder:

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

require 'yaml'

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

puts some_hash.to_yaml

Resulting in a YAML file like:

导致Y​​AML文件如:

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

As far as inserting the key into the hash, I'd probably restructure my "key chain" a little, to show the path where I want to insert it, and the name of the new key. Again, I'd rely on sorting to make sure things were in the right order prior to saving the YAML:

至于将密钥插入哈希,我可能会稍微重构我的“密钥链”,以显示我想要插入它的路径,以及新密钥的名称。同样,在保存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

Resulting in:

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

#3


2  

This is just as per the OP's need,but anytime can be modified as per the needs:

这只是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

Output:

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

UPDATE:

I found the more efficient code,which will reduce overhead of taking care of the line hash["nested"] = new_hash in my previous code :

我找到了更高效的代码,这将减少在我之前的代码中处理行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

#4


0  

I don't think ruby provides this functionality for free. You could do something like this, where you are creating an array of the existing hash keys, insert your new key into the array, then create a new hash with the newly ordered keys.

我不认为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

#1


7  

This is easiest by using the Hash's array representation:

使用Hash的数组表示最简单:

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

It could be wrapped into a function:

它可以包装成一个函数:

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

#2


2  

I often create YAML generators for large configurations for apps. For maintenance I need the fields sorted.

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

The solution I use when generating YAML in a sorted order, is to add the keys as necessary to get them in the right hash, or sub-hash. Then I create a new hash by sorting the key/value pairs and do to_yaml on it.

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

There's no point sorting a hash but sorting the temporary hash to be output prior to letting YAML have it does work and results in a more easily maintained file.

没有必要对散列进行排序,但在让YAML使用它之前对要输出的临时散列进行排序是有效的,并且会导致更容易维护的文件。

require 'yaml'

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

puts some_hash.to_yaml

Which outputs:

---
z: 1
a: 3

Sorting it prior to creating the YAML output:

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

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

Outputs:

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

Instead of puts, use File.write or embed the line in a block passed to File.open.

而不是put,使用File.write或将行嵌入传递给File.open的块中。


Regarding comments in YAML files: YAML doesn't support adding comments programmatically to the emitted output. Comments are for humans, and the # isn't mappable to a Ruby variable or object. Think of it this way: If we start with this YAML in a file called test.yaml:

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

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

And load it:

并加载它:

require 'pp'
require 'yaml'

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

pp obj

I get an obj looking like:

我得到一个obj看起来像:

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

There is no "comment" objects returned, and there are none that exist in Ruby that would fit into the hash, that exists in the YAML spec. We could arbitrarily cobble up a class that we call Comment, and try embedding it in the object as a key, but YAML wouldn't accept it as a comment, because the spec doesn't allow it. It would define it as a Ruby class and recreate it as that class, but it wouldn't be displayed as a # comment:

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

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

Outputting:

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

When I've needed comments in my emitted YAML configurations, I manually tweak them to add them later. For what you want to do, rather than do any manual tweaking, I'd recommend using more mnemonic or unique variable names that you can scan for in your document. You could even put in dummy entries that don't provide to anything worthwhile except acting as a place holder:

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

require 'yaml'

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

puts some_hash.to_yaml

Resulting in a YAML file like:

导致Y​​AML文件如:

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

As far as inserting the key into the hash, I'd probably restructure my "key chain" a little, to show the path where I want to insert it, and the name of the new key. Again, I'd rely on sorting to make sure things were in the right order prior to saving the YAML:

至于将密钥插入哈希,我可能会稍微重构我的“密钥链”,以显示我想要插入它的路径,以及新密钥的名称。同样,在保存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

Resulting in:

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

#3


2  

This is just as per the OP's need,but anytime can be modified as per the needs:

这只是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

Output:

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

UPDATE:

I found the more efficient code,which will reduce overhead of taking care of the line hash["nested"] = new_hash in my previous code :

我找到了更高效的代码,这将减少在我之前的代码中处理行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

#4


0  

I don't think ruby provides this functionality for free. You could do something like this, where you are creating an array of the existing hash keys, insert your new key into the array, then create a new hash with the newly ordered keys.

我不认为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