YAMLで配列をマージし、Ruby-
some_stuff: &some_stuff
- a
- b
- c
combined_stuff:
<<: *some_stuff
- d
- e
- f
結合された配列を[a,b,c,d,e,f]
エラーが表示されます:ブロックマッピングの解析中に予期されるキーが見つかりませんでした
YAMLで配列をマージするにはどうすればよいですか?
この投稿では、次のコンテキストを想定しています。
lfender6445は、YAMLファイル内の2つ以上のリストをマージし、それらのマージされたリストを解析時に1つの特異なリストとして表示することを望んでいます。
これは、YAMLアンカーをマッピングに割り当てるだけで取得できます。この場合、目的のリストはマッピングの子要素として表示されます。ただし、これには注意事項があります(以下の「落とし穴」を参照)。
次の例では、3つのマッピング(list_one, list_two, list_three
)およびこれらのマッピングを適切に参照する3つのアンカーとエイリアス。
YAMLファイルがプログラムにロードされると、必要なリストが取得されますが、ロード後に少し変更する必要がある場合があります(以下の落とし穴を参照)。
list_one:&id001 -a -b -c list_two:&id002 -e -f -g list_three:&id003 -h -i -j list_combined: -* id001 -* id002 -* id003
## list_combined "a"、 "b"、 "c" 、 "e"、 "f"、 "g" 、 "h"、 "i"、 "j"
このアプローチでは、YAMLのエイリアスとアンカー機能を使用して、マージされたリストを作成できます。
出力結果はリストのネストされたリストですが、これはflatten
メソッドを使用して簡単に変換できます。
flatten
メソッドの例flatten
;; 配列の配列をマージ/フラット化flatten
;; http://Ruby-doc.org/core-2.2.2/Array.html#method-i-flattenflatten
;; https://softwareengineering.stackexchange.com/a/254676/23884これは機能しません:
マージはマッピング用のYAML仕様でのみサポートされ、シーケンス用ではありません
マージキー<<
の後にキー/値区切り文字:
と参照である値が続くことにより、物事が完全に混ざり合った後、次のリストに進みます。同じインデントレベル
これは正しいYAMLではありません:
combine_stuff:
x: 1
- a
- b
したがって、構文例はYAML拡張提案としては意味がありません。
複数の配列をマージするようなことをしたい場合は、次のような構文を検討してください。
combined_stuff:
- <<: *s1, *s2
- <<: *s3
- d
- e
- f
ここで、s1
、s2
、s3
は、新しいシーケンスにマージしてからd
、e
を持つシーケンスのアンカー(図示せず)です。 f
が追加されます。しかし、YAMLはこれらの種類の構造の深さを最初に解決しているため、マージキーの処理中に利用できる実際のコンテキストはありません。処理済みの値(アンカーシーケンス)を添付できる配列/リストはありません。
@dreftymacによって提案されたアプローチを取ることができますが、これには、どのネストされたシーケンスをフラットにするかを知る必要があるという大きな欠点があります(つまり、ロードされたデータ構造のルートから親シーケンスへの「パス」を知ることによって)、または、ネストされた配列/リストを検索してロードされたデータ構造を再帰的に調べ、それらをすべて無差別にフラット化します。
IMOのより良い解決策は、タグを使用して、フラット化を行うデータ構造を読み込むことです。これにより、フラット化する必要があるものとそうでないものを明確に示し、このフラット化を読み込み中に行うか、アクセス中に行うかを完全に制御できます。どちらを選択するかは、実装の容易さと、時間とストレージスペースの効率の問題です。これは、 mergekey機能 を実装するために行う必要がある同じトレードオフであり、常に最適な単一のソリューションはありません。
例えば。 my ruamel.yaml
ライブラリは、セーフローダーを使用する際にロード中にブルートフォースmerge-dictsを使用します。これにより、通常のPython dicts。このマージは完了しなければなりません。 -front、データを複製します(スペース効率が悪い)が、値のルックアップは高速です。ラウンドトリップローダーを使用する場合、マージされていないマージをダンプできるようにするため、別々に保持する必要があります。ラウンドトリップロードの結果、スペース効率はよくなりますが、マージでdict自体に見つからないキーを検索する必要があるため、アクセスが遅くなります(これはキャッシュされないため、毎回行う必要がありますもちろん)比較的小さな構成ファイルでは、このような考慮事項はあまり重要ではありません。
以下は、pythonのリストにマージのようなスキームを実装します。タグflatten
を持つオブジェクトを使用し、オンザフライでリストでありtoflatten
であるアイテムに再帰します。これら2つのタグは、YAMLファイルを持つことができます:
l1: &x1 !toflatten
- 1
- 2
l2: &x2
- 3
- 4
m1: !flatten
- *x1
- *x2
- [5, 6]
- !toflatten [7, 8]
(フローとブロックスタイルのシーケンスの使用は完全に任意であり、読み込まれた結果に影響しません)。
キーm1
の値であるアイテムを繰り返し処理する場合、これはtoflatten
でタグ付けされたシーケンスに「再帰」しますが、他のリスト(エイリアスの有無)を単一のアイテムとして表示します。
Pythonそれを達成するためのコードの1つの可能な方法は:
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
、つまり、アンカーシーケンスにタグを付けます。これにより、基本的に別のデータ構造になります。
explicitタグを使用すると、YAMLマージキー<<
のように魔法をかけるよりもIMOの方が優れています。マージキーのように振る舞わないキー<<
を持つマッピングを持つYAMLファイルがある場合、他に何もする必要はありません。 C演算子を英語(またはその他の自然言語)の記述にマッピングするとき。
1つの項目のみをリストにマージする必要がある場合は、次を実行できます。
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
これは次のようにして達成できます。
# 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コメントへの回答)。
次の条件下で、マッピングをマージして、キーをリストに変換できます。
some_stuff: &some_stuff
a:
b:
c:
combined_stuff:
<<: *some_stuff
d:
e:
f:
{{ combined_stuff | list }}