是否有YAML语法可以共享列表或映射的一部分?

132

所以,我知道我可以做这样的事情:

sitelist: &sites
  - www.foo.com
  - www.bar.com

anotherlist: *sites

我有两个列表sitelistanotherlist,它们都包含www.foo.comwww.bar.com。然而,我想让anotherlist也包含www.baz.com,而不必重复添加www.foo.comwww.baz.com

这样做会在YAML解析器中导致语法错误:

sitelist: &sites
  - www.foo.com
  - www.bar.com

anotherlist: *sites
  - www.baz.com

仅使用锚点和别名似乎无法在不添加另一层子结构的情况下实现我想要的效果,例如:

sitelist: &sites
  - www.foo.com
  - www.bar.com

anotherlist:
  - *sites
  - www.baz.com
这意味着使用这个YAML文件的消费者必须意识到它。
是否有一种纯YAML方式来做类似于这样的事情?或者我需要使用一些后处理工具,比如实现变量替换或自动提升某些子结构?我已经在处理其他几个用例时进行了这种后处理,所以我并不完全排斥它。但我的YAML文件将由人类编写,而不是机器生成的,因此我希望在标准YAML语法之上尽量减少需要被用户记住的规则数量。
我还希望能够对映射执行类似的操作:
namedsites: &sites
  Foo: www.foo.com
  Bar: www.bar.com

moresites: *sites
  Baz: www.baz.com

我已经在YAML规范中进行了搜索,但没有找到任何信息,所以我怀疑答案只能是“不能这样做”。但如果有人有任何想法,那就太好了。


编辑:由于没有回答,我认为在YAML规范中没有发现我没有发现的任何内容,并且在YAML层面上无法完成此操作。因此,我将问题开放给后处理YAML以帮助解决此问题的想法,以防将来有人发现这个问题。


注意:这个问题也可以通过在YAML中使用锚点和别名来解决。另请参阅:如何合并YAML数组? - dreftymac
参见:https://dev59.com/FGw15IYBdhLWcg3wYasx - gerrit
5个回答

69

合并键类型 可能是您想要的。它使用特殊的<< 映射键来指示合并,允许使用映射别名(或此类别名的序列)作为初始化器来合并为单个映射。此外,您仍然可以明确覆盖值,或添加在合并列表中不存在的更多值。

需要注意的是,它适用于映射而不是序列,与您的第一个示例不同。这在您考虑时是有道理的,而且您的示例看起来可能不需要顺序。只需将您的序列值更改为映射键即可,如以下(未经测试)示例:

sitelist: &sites
  ? www.foo.com  # "www.foo.com" is the key, the value is null
  ? www.bar.com

anotherlist:
  << : *sites    # merge *sites into this mapping
  ? www.baz.com  # add extra stuff

需要注意一些事情。首先,由于<<是一个键,因此每个节点只能指定一次。其次,当使用序列作为值时,顺序很重要。在这个例子中这并不重要,因为没有相关联的值,但还是值得注意的。


啊,谢谢!这非常有帮助。不过很遗憾它不能用于序列。你说得对,对于这个例子来说顺序并不重要;我所拥有的概念上是一个集合,但它更接近于序列而不是映射。而且我得到的结构很重要(这就是为什么我不想只是添加另一层嵌套来合并我的结构),所以需要忽略(全部为空)值的映射并不真正适用。 - Ben
4
在当前官方 YAML 规范 http://www.yaml.org/spec/1.2/spec.html 中没有任何相关内容:其中没有包含单词"merge",也没有符号"<<”,也没有短语“key type”。不过,在 Python 的 yaml 包中,<< 语法确实可以工作。您知道在哪里可以了解这些额外功能的更多信息吗? - Ben
1
它不是直接在规范中描述的,而是在标签存储库中描述的。其他模式有一个通用的描述和链接。除了合并键之外,还有集合和有序集合;然而,YAML将集合视为映射类型(例如,上面的示例可以实现为集合)。你的语言是否允许你在生成的映射中交换键和值?即使你必须自己实现,我认为这样会更清晰;你至少已经将所有数据分组在一起了,你的YAML也会更加标准化。 - kittemon
集合并不是映射;映射是一组键值对的集合。当我在Python中使用yaml.load(...)时,我得到一个字典作为YAML映射的表示。是的,很容易将其后处理为一个集合,但我必须知道已经发生了这种情况(并且在读/写配置文件时的语义复杂性要高得多,如果规则是“集合被写成具有空值的映射”)。鉴于我需要在yaml.load(...)和使用结果数据之间进行后处理,无论我使用<<还是MERGE,我可能会坚持使用MERGE(我现在已经实现了)。 - Ben
2
是的,我发现!!set可以工作。虽然有太多晦涩的样板文件。这些文件应该由不一定是YAML专家的人来阅读/编写。人们将把他们的站点列表记为YAML列表,然后想要合并它们并将整个内容转换为集合,并且还要记得显式标记为集合。无论如何,我还有另外几个标准化的后处理功能与MERGE一起使用。谢谢你的帮助! - Ben
显示剩余2条评论

20

正如先前的回答所指出的那样,YAML 没有内置支持扩展列表。我提供了另一种自己实现的方法。请考虑以下内容:

defaults: &defaults
  sites:
    - www.foo.com
    - www.bar.com
     
setup1:
  <<: *defaults
  sites+:
    - www.baz.com

这将被处理成:

defaults:
  sites:
    - www.foo.com
    - www.bar.com

setup1:
  sites:
    - www.foo.com
    - www.bar.com
    - www.baz.com

这个想法是将以“+”结尾的键的内容合并到对应的没有“+”的键中。我用Python实现了它,并在此处发布。


3
注意:使用 YAML 中的锚点和别名也可以解决这个问题。 参见:如何合并 YAML 数组? - dreftymac
23
这是否意味着这种方法只能与一个合并sitessites+的单独工具一起使用?我的意思是,这个工具必须由用户实现,因为这不是默认的yaml行为? - stan0
9
这不是 YAML 的默认行为。 - Jake

10

(为了帮助将来搜索此问题的任何人,我自己回答了这个问题)

没有纯粹的YAML方法可以实现这一点,因此我将在YAML解析器和实际使用配置文件的代码之间实现“语法转换”。因此,我的核心应用程序不必担心任何人性化的冗余避免措施,而可以直接对生成的结构进行操作。

我将使用以下结构:

foo:
  MERGE:
    - - a
      - b
      - c
    - - 1
      - 2
      - 3

这将转换为等效的内容:

foo:
  - a
  - b
  - c
  - 1
  - 2
  - 3

或者,使用地图:

foo:
  MERGE:
    - fork: a
      spoon: b
      knife: c
    - cup: 1
      mug: 2
      glass: 3

会被转换为:

foo:
  fork: a
  spoon: b
  knife: c
  cup: 1
  mug: 2
  glass: 3
更正式地说,在从配置文件获取本地对象之后调用YAML解析器,但在将对象传递给应用程序的其余部分之前,我的应用程序将遍历对象图,查找包含单个键“MERGE”的映射。与MERGE相关联的值必须是列表的列表或地图的列表;任何其他子结构都是错误的。
在列表-列表情况下,包含MERGE的整个映射将被替换为按它们出现的顺序连接在一起的子列表。
在列表-地图情况下,包含MERGE的整个映射将被替换为一个包含所有子地图中的键/值对的单个地图。在键重叠的情况下,将使用在MERGE列表中最后出现的子地图中的值。
以上给出的示例并不太有用,因为您可以直接编写所需的结构。 它更可能出现为:
foo:
  MERGE:
    - *salt
    - *pepper

允许您创建一个包含节点saltpepper中所有内容的列表或映射,以便在其他地方使用。

(我不断提供foo:外部映射,以说明MERGE必须是其映射中唯一的键,这意味着除非没有其他顶级名称,否则MERGE不能作为顶级名称出现)


8
为了澄清这里两个答案中的一些内容,直接在YAML列表中不支持此功能(但字典是支持的,请参见kittemon的答案)。

注意:这个问题也可以通过在YAML中使用锚点和别名来解决。另请参阅:如何合并YAML数组? - dreftymac

5
为了补充Kittemon的回答,需要注意可以使用另一种语法创建具有null值的映射。
foo:
    << : myanchor
    bar:
    baz:

与建议的语法不同

foo:
    << : myanchor
    ? bar
    ? baz

像Kittemon的建议一样,这将允许您在映射中使用对锚点的引用,并避免序列问题。我发现自己需要这样做是因为发现Symfony Yaml组件v2.4.4不识别? bar语法。


1
myanchor看起来是什么样子? - ssc
我的锚点看起来像 &myanchor,在声明时是这样的,在使用时则像 *myanchor。以下是一个类似 JSON 的示例语法,因为注释不允许代码块:{ original: &myanchor { foo: "Hi", bar: "World" }, later: { <<: *myanchor, foo: "Bye"} } - Leo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接