Ansible Tips: list of dictionaryとdictionaryを相互に変換する (loopを使用しないロジックに更新)

2021/12: 1階層の変換をloopを使用しないロジックに置き換え


# dictonaryのlistを、dictonary型に変換したり、戻したりする例

それぞれのデータ型はメリット、デメリットが存在する。

##参考
- Ansible Tips: dictionary や list_of_dictionaries 構造に対する値の参照とループ操作
    - https://qiita.com/hiroyuki_onodera/items/b42f728c2270a7bccd97

例えばdictonary型ではdeep mergeが使えるのでデータの統合が容易、など
ここでは、それらを相互に変換するロジックを考える。


## 1階層のデータ

### データ

以下の様なdictonaryのlistを

```
- name: foo1
  attr: bar1
- name: foo2
  attr: bar2
```

以下の様なdictonaryと相互に変換することを考える

```
foo1:
  name: foo1
  attr: bar1
foo2:
  name: foo2
  attr: bar2
```

- ただしdictionaryでは順序の保証がないので、listに戻した際にも順序は保証されない。

## playbook例

```yaml:site.yml
---
- hosts: all
  vars:

    hoge:
      - name: foo1
        attr: bar1
      - name: foo2
        attr: bar2

    ansible_python_interpreter: '/usr/bin/python3'

  tasks:
    - name: 元データ
      debug:
        var: hoge

    - name: list of dict型をdict型に変換
      set_fact:
        hoge_dict: '{{ dict( hoge|map(attribute="name")|zip(hoge) ) }}'

    - name: dict型に変換後
      debug:
        var: hoge_dict

    - name: dict型からlist of dict型に戻す
      set_fact:
        hoge_dict2list: '{{ hoge_dict.values() }}'

    - name: list of dict型に変換後
      debug:
        var: hoge_dict2list
```

```
$ ansible-playbook -i localhost, -c local site.yml 

PLAY [all] ***************************************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [localhost]

TASK [元データ] **********************************************************************************************************************************************
ok: [localhost] => {
    "hoge": [
        {
            "attr": "bar1",
            "name": "foo1"
        },
        {
            "attr": "bar2",
            "name": "foo2"
        }
    ]
}

TASK [list of dict型をdict型に変換] **************************************************************************************************************************
ok: [localhost]

TASK [dict型に変換後] ****************************************************************************************************************************************
ok: [localhost] => {
    "hoge_dict": {
        "foo1": {
            "attr": "bar1",
            "name": "foo1"
        },
        "foo2": {
            "attr": "bar2",
            "name": "foo2"
        }
    }
}

TASK [dict型からlist of dict型に戻す] ************************************************************************************************************************
ok: [localhost]

TASK [list of dict型に変換後] ********************************************************************************************************************************
ok: [localhost] => {
    "hoge_dict2list": [
        {
            "attr": "bar1",
            "name": "foo1"
        },
        {
            "attr": "bar2",
            "name": "foo2"
        }
    ]
}

PLAY RECAP ***************************************************************************************************************************************************
localhost                  : ok=6    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


```

# 2階層のデータ

with_subelements が使える2階層の場合、list of dict -> dictはinclude_tasksなどを使わなくとも変換可能

## tasks例: 2階層list of dict -> dict

```yaml:site.yml
---
- hosts: all
  gather_facts: false
  vars:

    hoge2:
    - name: foo1
      attr: foo1_attr
      member:
      - name: bar11
        attr: baz11
      - name: bar12
        attr: baz12
    - name: foo2
      attr: foo2_attr
      member:
      - name: bar21
        attr: baz21
      - name: bar22
        attr: baz22

  tasks:
  - set_fact: { fuga2: {} } # loopで出力を追加していく初期値
  - vars:                   # task変数定義
      index1: member        #   2階層目を示すkey
      key0: name            #   1階層目の要素を識別するキー。dictのキーとして使用
      key1: name            #   2階層目の要素を識別するキー。dictのキーとして使用
    with_subelements:       # 2階層ループ
    - "{{ hoge2 }}"         #   対象のlist of dict
    - "{{ index1 }}"        #   2階層目を示すkey。task変数で指定されたもの
    set_fact:
      fuga2: "{{ 
        fuga2 
        | combine( 
          { (item[0][key0]): item[0] } 
        | combine( 
          { (item[0][key0]): 
            { index1: 
              { (item[1][key1]): item[1] } 
            } 
          }
        , recursive=true)
        , recursive=true) 
        }}"

  - debug:
      var: fuga2
```

- loopで、空dictのfuga2にdictを追加していく
- |combine()にてdict型をマージ
 

## 実行例

```
TASK [set_fact] *****************************************************************************************************
ok: [localhost] => (item=[{'name': 'foo1', 'attr': 'foo1_attr'}, {'name': 'bar11', 'attr': 'baz11'}])
ok: [localhost] => (item=[{'name': 'foo1', 'attr': 'foo1_attr'}, {'name': 'bar12', 'attr': 'baz12'}])
ok: [localhost] => (item=[{'name': 'foo2', 'attr': 'foo2_attr'}, {'name': 'bar21', 'attr': 'baz21'}])
ok: [localhost] => (item=[{'name': 'foo2', 'attr': 'foo2_attr'}, {'name': 'bar22', 'attr': 'baz22'}])

TASK [debug] ********************************************************************************************************
ok: [localhost] => {
    "fuga2": {
        "foo1": {
            "attr": "foo1_attr",
            "member": {
                "bar11": {
                    "attr": "baz11",
                    "name": "bar11"
                },
                "bar12": {
                    "attr": "baz12",
                    "name": "bar12"
                }
            },
            "name": "foo1"
        },
        "foo2": {
            "attr": "foo2_attr",
            "member": {
                "bar21": {
                    "attr": "baz21",
                    "name": "bar21"
                },
                "bar22": {
                    "attr": "baz22",
                    "name": "bar22"
                }
            },
            "name": "foo2"
        }
    }
}
```


## tasks例: 2階層 dict -> list of dict
2階層 dictからlist of dictへの戻し

playbook続き

```yaml
   - set_fact: { out_list: [] }
   - vars:
      in_dict: "{{ fuga2 }}"
      index1: member
    loop: "{{ in_dict|dict2items|map(attribute='value')|list }}"
    set_fact:
      out_list: "{{ out_list + [ 
        item
        |combine(
          { index1: item[index1]|dict2items|map(attribute='value')|list } 
        , recursive=true)
        ] 
        }}"

  - debug:
      var: out_list
```

- loopで、空listのout_listに要素を追加していく
- \+ [xxxx] にてlist型に要素を追加
 
出力続き

```
TASK [set_fact] *****************************************************************************************************
ok: [localhost] => (item={'name': 'foo1', 'attr': 'foo1_attr', 'member': {'bar11': {'name': 'bar11', 'attr': 'baz11'}, 'bar12': {'name': 'bar12', 'attr': 'baz12'}}})
ok: [localhost] => (item={'name': 'foo2', 'attr': 'foo2_attr', 'member': {'bar21': {'name': 'bar21', 'attr': 'baz21'}, 'bar22': {'name': 'bar22', 'attr': 'baz22'}}})

TASK [debug] ********************************************************************************************************
ok: [localhost] => {
    "out_list": [
        {
            "attr": "foo1_attr",
            "member": [
                {
                    "attr": "baz11",
                    "name": "bar11"
                },
                {
                    "attr": "baz12",
                    "name": "bar12"
                }
            ],
            "name": "foo1"
        },
        {
            "attr": "foo2_attr",
            "member": [
                {
                    "attr": "baz21",
                    "name": "bar21"
                },
                {
                    "attr": "baz22",
                    "name": "bar22"
                }
            ],
            "name": "foo2"
        }
    ]
}
```

out_listをYAMLで書くと

```yaml
---
- attr: foo1_attr
  member:
  - attr: baz11
    name: bar11
  - attr: baz12
    name: bar12
  name: foo1
- attr: foo2_attr
  member:
  - attr: baz21
    name: bar21
  - attr: baz22
    name: bar22
  name: foo2
```