備忘
概要
以前の以下の記事の更新。
https://qiita.com/hiroyuki_onodera/items/8588c44c9886aa67cb96
Ansible Tips: データ構造変換にはjinja2 for loopとfrom_yamlフィルターの組み合わせが使いやすい
上ではfrom_yamlフィルターを使った2段階変換を行っていたが、YAMLを経由しないで直接変換する例を示す。
元のロジックの例 (YAML経由変換)
元の、一旦YAMLデータを作成し、構造化データに変換する例
- debug:
msg: "{{ target }}"
vars:
source_data:
- - hoge1
- - foo: FOO1
bar: BAR1
- foo: FOO2
bar: BAR2
- - hoge2
- - foo: FOO3
bar: BAR3
- foo: FOO4
bar: BAR4
target_yml: |
{% for i0 in source_data %}
{{ i0[0] }}:
{% for i1 in i0[1] %}
- {{ (i1['foo'] + '/' + i1['bar']) | to_json(ensure_ascii=false, sort_keys=false) }}
{% endfor %}
{% endfor %}
target: "{{ target_yml | from_yaml }}"
この例では、一旦YAMLデータのtarget_yamlを作成し、その後from_yamlで構造化データとして読み込み直しの2段階データ変換。
YAMLのブロックスタイルなども利用可能なので結構ユニークな使い方ができるが、反面、to_jsonにてデフォルトではキーが勝手にソートされるなど、それなりに癖がある。この例ではソートはされないが。
直接変換の例 (YAMLを経由しない)
以下のように書けばYAMLを経由せず、1段階で変換可能
- debug:
msg: "{{ target2 }}"
vars:
source_data:
- - hoge1
- - foo: FOO1
bar: BAR1
- foo: FOO2
bar: BAR2
- - hoge2
- - foo: FOO3
bar: BAR3
- foo: FOO4
bar: BAR4
target2: |
{% set ns = namespace(output={}, lst=[]) %}
{% for i0 in source_data %}
{%- set ns.lst = [] -%}
{% for i1 in i0[1] %}
{%- set ns.lst = ns.lst + [i1['foo'] + '/' + i1['bar']] -%}
{% endfor %}
{%- set ns.output = ns.output | combine({i0[0]: ns.lst}) -%}
{% endfor %}
{{ ns.output }}
考慮点
- for loop内で更新した変数の値をloop外に持ち出すことで対応。jinja2におけるloop外への値の持ち出しはいくつか方法があるが、ここでは判り易いと思えるnamespaceを利用。
- 変換箇所が不必要な改行やスペースなどを生み出さないように、適宜{%- -%}などで前後の改行空白を削除する。for loop内のそれぞれのsetの箇所において{%- -%}とすれば十分な様子。
- データの追加はlistなら+などが、dictならcombineなどが利用可能。
- 最後に変換後の値を{{ }}にて変数に設定。
namespace参考
https://jinja.palletsprojects.com/en/3.1.x/templates/#jinja-globals.namespace
https://qiita.com/masa2223/items/129012d5539a8724556d
https://qiita.com/waro_a2606/items/ae92dc26841a302ca95e