如何合并YAML数组?

时间:2014-06-06 20:33:17

标签: yaml

我想在YAML中合并数组,并通过ruby加载它们 -

some_stuff: &some_stuff
 - a
 - b
 - c

combined_stuff:
  <<: *some_stuff
  - d
  - e
  - f

我想将组合数组设为[a,b,c,d,e,f]

我收到错误:解析块映射时找不到预期的键

如何在YAML中合并数组?

7 个答案:

答案 0 :(得分:17)

上下文

本文假设以下背景:

  • python 2.7
  • python YAML解析器

问题

lfender6445希望合并YAML文件中的两个或多个列表,并拥有这些列表 合并列表在解析时显示为一个单数列表。

解决方案(解决方法)

这可以通过将YAML锚分配给映射来获得,其中 所需列表显示为映射的子元素。然而,有一些警告(参见&#34;陷阱&#34; infra)。

在下面的示例中,我们有三个映射(list_one, list_two, list_three)和三个锚点 和适当引用这些映射的别名。

当YAML文件加载到程序中时,我们得到了我们想要的列表,但是 加载后可能需要稍加修改(见下面的陷阱)。

实施例

原始YAML文件

  list_one: &id001
   - a
   - b
   - c

  list_two: &id002
   - e
   - f
   - g

  list_three: &id003
   - h
   - i
   - j

  list_combined:
      - *id001
      - *id002
      - *id003

YAML.safe_load

之后的结果
## list_combined
  [
    [
      "a",
      "b",
      "c"
    ],
    [
      "e",
      "f",
      "g"
    ],
    [
      "h",
      "i",
      "j"
    ]
  ]

陷阱

结论

此方法允许使用YAML的别名和锚点功能创建合并列表。

虽然输出结果是嵌套的列表列表,但可以使用flatten方法轻松转换。

另见

flatten方法

的示例

答案 1 :(得分:10)

这不起作用:

    只有映射的YAML规范支持
  1. merge,而不支持序列

  2. 您通过合并键<<完全混合了一些东西 后跟键/值分隔符:和值为a的值 引用然后继续使用相同缩进的列表 水平

  3. 这不正确YAML:

    combine_stuff:
      x: 1
      - a
      - b
    

    因此,您的示例语法甚至不能用作YAML扩展提议。

    如果您想要合并多个数组,可能需要考虑如下语法:

    combined_stuff:
      - <<: *s1, *s2
      - <<: *s3
      - d
      - e
      - f
    

    其中s1s2s3是您的序列(未显示)的锚点 想要合并为一个新序列,然后拥有def 附加到那。但是YAML正在解决这些结构的深度问题 首先,因此在处理过程中没有真正的上下文 合并密钥。您没有可用的阵列/列表 可以将处理后的值(锚定序列)附加到。

    您可以采用@dreflymac提出的方法,但这会给您带来巨大的劣势 不知何故需要知道哪些嵌套序列变平(即通过从根知道&#34;路径&#34;) 加载的数据结构到父序列),或者递归地遍历加载的数据结构 数据结构搜索嵌套数组/列表并不加区别地压平它们。

    更好的解决方案IMO将使用标签来加载数据结构 为你做扁平化这允许清楚地表示什么 需要被夷为平地,什么不是,并让你完全控制 这种展平是在装载过程中完成还是在完成过程中完成 访问。选择哪一个是易于实施的问题 时间和存储空间的效率。这是需要进行的相同交易 用于实现merge key feature和 没有单一的解决方案永远是最好的。

    E.g。我的ruamel.yaml库在使用期间使用了强力合并 使用安全加载程序时加载,导致合并 字典是普通的Python dicts。这种合并必须要做 预先,并复制数据(空间效率低),但价值快 抬头。使用往返加载程序时,您希望能够转储 合并未合并,所以他们需要保持独立。这个词典就像 由于往返加载而加载的数据结构是空间 高效但访问速度较慢,因为它需要尝试查找密钥 在合并中没有在dict中找到(并且这不是缓存的,所以 它需要每次都完成)。当然这样的考虑是 对于相对较小的配置文件不是很重要。

    下面使用标记为flatten的对象为python中的列表实现类似合并的方案 即时递归到列表和标记toflatten的项目。使用这两个标签 你可以有YAML文件:

    l1: &x1 !toflatten
      - 1 
      - 2
    l2: &x2
      - 3 
      - 4
    m1: !flatten
      - *x1
      - *x2
      - [5, 6]
      - !toflatten [7, 8]
    

    (使用flow vs block样式序列完全是任意的,并且对它没有影响 加载结果)。

    当迭代作为键m1的值的项时 &#34;递归&#34;进入标有toflatten的序列,但显示 其他列表(别名或非别名)作为单个项目。

    Python代码实现这一目标的一种可能方式是:

    import sys
    from pathlib import Path
    import ruamel.yaml
    
    yaml = ruamel.yaml.YAML()
    
    
    @yaml.register_class
    class Flatten(list):
       yaml_tag = u'!flatten'
       def __init__(self, *args):
          self.items = args
    
       @classmethod
       def from_yaml(cls, constructor, node):
           x = cls(*constructor.construct_sequence(node, deep=True))
           return x
    
       def __iter__(self):
           for item in self.items:
               if isinstance(item, ToFlatten):
                   for nested_item in item:
                       yield nested_item
               else:
                   yield item
    
    
    @yaml.register_class
    class ToFlatten(list):
       yaml_tag = u'!toflatten'
    
       @classmethod
       def from_yaml(cls, constructor, node):
           x = cls(constructor.construct_sequence(node, deep=True))
           return x
    
    
    
    data = yaml.load(Path('input.yaml'))
    for item in data['m1']:
        print(item)
    

    输出:

    1
    2
    [3, 4]
    [5, 6]
    7
    8
    

    正如您所看到的,您可以按照需要展平的顺序看到 可以使用标记序列的别名,也可以使用标记 序列。 YAML不允许你这样做:

    - !flatten *x2
    

    ,即标记一个 锚定序列,因为这基本上使它成为一个不同的 数据结构。

    使用显式标签比IMO更好 使用YAML合并键<<。如果没有其他你现在必须通过 如果您碰巧有一个带有密钥的映射的YAML文件,那就会发生错误 您不想表现为合并密钥的<<,例如当你做一个 C运算符映射到英语(或其他一些自然语言)的描述。

答案 2 :(得分:1)

另一种在 python 中启用合并数组的方法是定义一个 !flatten 标签。 (这使用 PyYAML,与上面 Anthon 的回答不同。如果您无法控制在支持中使用哪个包,例如 anyconfig,这可能是必要的。

import yaml

yaml.add_constructor("!flatten", construct_flat_list)

def flatten_sequence(sequence: yaml.Node) -> Iterator[str]:
    """Flatten a nested sequence to a list of strings
        A nested structure is always a SequenceNode
    """
    if isinstance(sequence, yaml.ScalarNode):
        yield sequence.value
        return
    if not isinstance(sequence, yaml.SequenceNode):
        raise TypeError(f"'!flatten' can only flatten sequence nodes, not {sequence}")
    for el in sequence.value:
        if isinstance(el, yaml.SequenceNode):
            yield from flatten_sequence(el)
        elif isinstance(el, yaml.ScalarNode):
            yield el.value
        else:
            raise TypeError(f"'!flatten' can only take scalar nodes, not {el}")

def construct_flat_list(loader: yaml.Loader, node: yaml.Node) -> List[str]:
    """Make a flat list, should be used with '!flatten'

    Args:
        loader: Unused, but necessary to pass to `yaml.add_constructor`
        node: The passed node to flatten
    """
    return list(flatten_sequence(node))

这种递归扁平化利用了 PyYAML 文档结构,它将所有数组解析为 SequenceNode,所有值解析为 ScalarNode。 可以在以下测试函数中测试(和修改)该行为。

import pytest
def test_flatten_yaml():
    # single nest
    param_string = """
    bread: &bread
      - toast
      - loafs
    chicken: &chicken
      - *bread
    midnight_meal: !flatten
      - *chicken
      - *bread
    """
    params = yaml.load(param_string)
    assert sorted(params["midnight_meal"]) == sorted(
        ["toast", "loafs", "toast", "loafs"]
    )

答案 3 :(得分:0)

如果您只需要将一项合并到列表中,则可以

fruit:
  - &banana
    name: banana
    colour: yellow

food:
  - *banana
  - name: carrot
    colour: orange

产生

fruit:
  - name: banana
    colour: yellow

food:
  - name: banana
    colour: yellow
  - name: carrot
    colour: orange

答案 4 :(得分:0)

您可以按照以下步骤实现:

# note: no dash before commands
some_stuff: &some_stuff |-
    a
    b
    c

combined_stuff:
  - *some_stuff
  - d
  - e
  - f

我一直在gitlab-ci.yml上使用它(回答有关问题的@ rink.attendant.6评论)。

答案 5 :(得分:0)

这是我在 docker-compose.yml 文件中实现它的方法。

“合并列表”部分位于环境变量部分。我认为棘手(也很酷的部分)是我能够在 *snapshots-repository-path 中使用对 &es-env-base 的引用。

一般而言 - 更喜欢使用 long syntax 而不是 short syntax,因为它支持使用 YAML *references

version: "3.7"

### ------------------------------------------------------------------
### Variables
### ------------------------------------------------------------------
x-variables:
  exposed-port: &exposed-port 9200
  es-base: &es-base
    image: docker.elastic.co/elasticsearch/elasticsearch:7.9.1
    ulimits:
      memlock:
        soft: -1
        hard: -1
    networks:
      - elastic
  data-path: &data-path /usr/share/elasticsearch/data      
  snapshots-repository-path: &snapshots-repository-path /usr/share/elasticsearch/backup
  volume-snapshots-repository: &volume-snapshots-repository 
    - type: volume
      source: snapshots-repository
      target: *snapshots-repository-path  
  services-es-env: &es-env-base
    "cluster.name": "es-docker-cluster"
    "cluster.initial_master_nodes": "es01,es02"
    "bootstrap.memory_lock": "true"
    "ES_JAVA_OPTS": "-Xms512m -Xmx512m"
    "ELASTIC_PASSWORD": "esbackup-password"
    "xpack.security.enabled": "true"
    "path.repo": *snapshots-repository-path
### ------------------------------------------------------------------

services:
  es01: # master
    <<: *es-base
    container_name: es01
    environment:
      <<: *es-env-base
      node.name: es01
      discovery.seed_hosts: es02
    volumes:
      - <<: *volume-snapshots-repository 
      - type: volume
        source: data01
        target: *data-path
    ports:
      - published: *exposed-port
        target: 9200
        protocol: tcp
        mode: host

  es02:
    <<: *es-base
    container_name: es02
    environment:
      <<: *es-env-base
      node.name: es02
      discovery.seed_hosts: es01
    volumes:
      - <<: *volume-snapshots-repository 
      - type: volume
        source: data02
        target: *data-path

volumes:
  data01:
    driver: local
  data02:
    driver: local
  snapshots-repository:
    driver: local

networks:
  elastic:
    driver: bridge

答案 6 :(得分:-1)

您可以合并映射,然后在以下条件下将其密钥转换为列表:

  • 如果您正在使用jinja2模板和
  • 如果项目顺序不重要
x/def