用于<<
指示别名映射应合并到当前映射不是核心 Yaml 规范的一部分,但它是标签存储库的一部分。
Ruby 提供的当前 Yaml 库 - Psych - 提供了dump
和load
方法,这些方法允许轻松序列化和反序列化 Ruby 对象,并使用标签存储库中的各种隐式类型转换,包括<<
合并哈希。如果需要,它还提供工具来执行更多低级 Yaml 处理。不幸的是,它不允许有选择地禁用或启用标签存储库的特定部分——这是一个全有或全无的事情。尤其是对 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
然后你会像这样使用它:
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
(更新: 另一个问题询问如何使用多个锚点来执行此操作,我想出了一种在序列化时保留锚点名称的可能更好的方法。)