113

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

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

anotherlist: *sites

并且 havesitelistanotherlist都包含www.foo.comand www.bar.com。但是,我真正想要的是anotherlist包含www.baz.com而不必重复www.foo.comand www.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 文件将由人类编写,而不是机器生成,因此我希望在标准 YAML 语法之上尽量减少用户需要记住的规则数量。

我也希望能够用地图做类似的事情:

namedsites: &sites
  Foo: www.foo.com
  Bar: www.bar.com

moresites: *sites
  Baz: www.baz.com

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


编辑:由于没有答案,我假设没有人发现我在 YAML 规范中没有的任何内容,并且这不能在 YAML 层完成。因此,我提出了对 YAML 进行后处理以帮助解决此问题的问题,以防将来有人发现此问题。

4

5 回答 5

61

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

重要的是要注意它适用于映射,而不是作为您的第一个示例的序列。当您考虑它时,这是有道理的,并且您的示例看起来可能无论如何都不需要顺序。只需将您的序列值更改为映射键就可以解决问题,如下面的(未经测试的)示例所示:

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

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

于 2012-03-01T23:34:37.400 回答
19

正如前面的答案所指出的,在 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 中实现了这个并 在此处发布。

于 2017-01-18T20:11:06.280 回答
8

(回答我自己的问题,以防我使用的解决方案对将来搜索此问题的任何人有用)

由于没有纯 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 解析器从配置文件中获取本机对象之后,但在将对象传递给应用程序的其余部分之前,我的应用程序将遍历对象图以查找包含单个 key 的映射MERGE。关联的值MERGE必须是列表列表或地图列表;任何其他子结构都是错误的。

在 list-of-lists 的情况下,包含的整个 mapMERGE将被替换为按出现顺序连接在一起的子列表。

在 list-of-maps 的情况下,包含的整个映射MERGE将被一个包含子映射中所有键/值对的单个映射替换。如果键中有重叠,MERGE则将使用列表中最后出现的子映射中的值。

上面给出的示例没有那么有用,因为您可以直接编写您想要的结构。它更有可能显示为:

foo:
  MERGE:
    - *salt
    - *pepper

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

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

于 2012-02-17T03:20:50.913 回答
7

为了从这里的两个答案中澄清一些事情,YAML 不直接支持列表(但它支持字典,请参阅 kittemon 的答案)。

于 2014-08-20T19:35:53.107 回答
5

要搭载 Kittemon 的答案,请注意,您可以使用替代语法创建具有空值的映射

foo:
    << : myanchor
    bar:
    baz:

而不是建议的语法

foo:
    << : myanchor
    ? bar
    ? baz

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

于 2014-09-24T18:33:37.757 回答