读取和写入YAML文件而不破坏锚点和别名?

我需要打开一个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在现实中看起来有点像一个看起来很暗淡的游戏领域。

使用<<来表示别名映射应该合并到当前映射中不是核心Yaml规范的一部分 ,但它是标记存储库的一部分 。

Ruby-Psych提供的当前Yaml库提供了dumpload方法,可以轻松地对Ruby对象进行序列化和反序列化,并在标记库中使用各种隐式类型转换,包括<<合并哈希。 它还提供了在需要时进行更多低级Yaml处理的工具。 不幸的是,它不容易选择性地禁用或启用标签存储库的特定部分 - 这是一个全有或全无的事情。 特别是对<<处理哈希的处理非常好 。

实现你想要的东西的一种方法是提供你自己的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 

然后你会像这样使用它:

 tree = Psych.parse your_data data = ToRubyNoMerge.new.accept tree 

使用您示例中的Yaml, data看起来就像

 {"defaults"=>{"foo"=>"bar", "zip"=>"button"}, "node"=>{"<<"=>{"foo"=>"bar", "zip"=>"button"}, "foo"=>"other"}} 

注意<<作为文字键。 此外, data["defaults"]键下的散列与data["node"]["<<"]键下的散列相同 ,即它们具有相同的object_id 。 您现在可以根据需要操作数据,当您将其写为Yaml时,锚点和别名仍将存在,尽管锚名称将更改:

 data['node']['foo'] = "yet another" puts Yaml.dump data 

产生(Psych使用哈希的object_id来确保唯一的锚名称(当前版本的Psych现在使用连续数字而不是object_id )):

 --- defaults: &2151922820 foo: bar zip: button node: <<: *2151922820 foo: yet another 

如果您想控制锚名称,可以提供自己的Psych::Visitors::Emitter 。 这是一个基于您的示例的简单示例,并假设只有一个锚点:

 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 

输出是:

 --- defaults: &defaults foo: bar zip: button node: <<: *defaults foo: yet another 

更新: 另一个问题是如何使用多个锚点进行此操作,我在序列化时提出了一种更好的方法来保留锚名称 。)

YAML有别名,他们可以往返,但你通过哈希合并禁用它。 <<作为映射键似乎是YAML的非标准扩展(在1.8的syck和1.9的心理中)。

 require 'rubygems' require 'yaml' yaml = < 

版画

 --- defaults: &id001 zip: button foo: bar node: *id001 

但是你的数据中的<<将别名哈希合并为一个不再是别名的新哈希。

你有没有尝试过Psych ? 这里有另一个心理问题 。