我需要打开一个YAML文件,其中包含别名:
defaults: &defaults
foo: bar
zip: button
node:
<<: *defaults
foo: other
这显然扩展为以下的YAML文档:
defaults:
foo: bar
zip: button
node:
foo: other
zip: button
哪个YAML::load
将其读作。
我需要在这个YAML文档中设置新密钥,然后将其写回磁盘,尽可能保留原始结构。
我查看了YAML::Store,但这完全破坏了别名和锚点。
是否有任何可用的内容:
thing = Thing.load("config.yml")
thing[:node][:foo] = "yet another"
将文档保存为:
defaults: &defaults
foo: bar
zip: button
node:
<<: *defaults
foo: yet another
我选择使用YAML,因为它很好地处理了这个别名,但是编写包含别名的YAML在现实中看起来有点像一个看起来很暗淡的游戏场。
答案 0 :(得分:12)
使用<<
来表示别名映射应该合并到当前映射中,而不是核心Yaml规范的一部分,但它是part of the tag repository。
Ruby-Psych提供的当前Yaml库提供了dump
和load
方法,这些方法允许轻松地对Ruby对象进行序列化和反序列化,并在标记库中使用各种隐式类型转换,包括{{ 1}}合并哈希。它还提供了在需要时进行更多低级Yaml处理的工具。不幸的是,它不容易选择性地禁用或启用标签存储库的特定部分 - 这是一个全有或全无的事情。特别是handling of <<
is pretty baked in to the handling of hashes。
实现目标的一种方法是提供自己的Psych <<
类的子类并覆盖此方法,以便它只将ToRuby
的映射键视为文字。这涉及在Psych中覆盖私有方法,所以你需要小心一点:
<<
然后你会像这样使用它:
require 'psych'
class ToRubyNoMerge < Psych::Visitors::ToRuby
def revive_hash hash, o
@st[o.anchor] = hash if o.anchor
o.children.each_slice(2) { |k,v|
key = accept(k)
hash[key] = accept(v)
}
hash
end
end
使用您示例中的Yaml,tree = Psych.parse your_data
data = ToRubyNoMerge.new.accept tree
将看起来像
data
请注意{"defaults"=>{"foo"=>"bar", "zip"=>"button"},
"node"=>{"<<"=>{"foo"=>"bar", "zip"=>"button"}, "foo"=>"other"}}
作为文字键。此外,<<
密钥下的哈希是相同的哈希,与data["defaults"]
密钥下的哈希相同,即它们具有相同的data["node"]["<<"]
。您现在可以根据需要操作数据,当您将其写为Yaml时,锚点和别名仍将存在,尽管锚名称将会更改:
object_id
产生(Psych使用哈希的data['node']['foo'] = "yet another"
puts Yaml.dump data
来确保唯一的锚名称(当前版本的Psych现在使用连续数字而不是object_id
)):
object_id
如果您想控制锚名称,可以提供自己的Psych::Visitors::Emitter
。这是一个基于您的示例的简单示例,并假设只有一个锚点:
---
defaults: &2151922820
foo: bar
zip: button
node:
<<: *2151922820
foo: yet another
与上面的修改后的class MyEmitter < Psych::Visitors::Emitter
def visit_Psych_Nodes_Mapping o
o.anchor = 'defaults' if o.anchor
super
end
def visit_Psych_Nodes_Alias o
o.anchor = 'defaults' if o.anchor
super
end
end
哈希一起使用时:
data
输出是:
#create an AST based on the Ruby data structure
builder = Psych::Visitors::YAMLTree.new
builder << data
ast = builder.tree
# write out the tree using the custom emitter
MyEmitter.new($stdout).accept ast
(更新: another question询问如何使用多个锚点执行此操作,我想出了possibly better way to keep anchor names when serializing。)
答案 1 :(得分:3)
YAML有别名,他们可以往返,但你通过哈希合并禁用它。 <<
作为映射键似乎是YAML的非标准扩展(在1.8的syck和1.9的心理中)。
require 'rubygems'
require 'yaml'
yaml = <<EOS
defaults: &defaults
foo: bar
zip: button
node: *defaults
EOS
data = YAML.load yaml
print data.to_yaml
打印
---
defaults: &id001
zip: button
foo: bar
node: *id001
但是数据中的<<
会将别名哈希合并为一个不再是别名的新哈希值。
答案 2 :(得分:1)