Search code examples
dictionaryansiblemappingjinja2

How to apply dictionary to list of items with Jinja filter?


I have a list of items, which all appear as keys in a certain dictionary. I would like to use Jinja2 filters to lookup each list item in the dictionary and return the corresponding value, in a list.

In python this would be:

[my_dict[x] for x in my_list]

What is the Jinja equivalent?

my_list | map(my_dict) does not work.

Here's a sample playbook.

---
- hosts: localhost
  connection: local
  vars:
    my_dict:
      a: 1
      b: 2
      c: 3
    my_list:
      - a
      - c
  tasks:
  - assert:
      that: 
      - "{{ actual == expected }}"
    vars:
      # [my_dict[x] for x in my_list]
      actual: "{{ my_list | map(my_dict) | list }}"
      expected:
        - 1
        - 3

If you run this, you get:

fatal: [localhost]: FAILED! => {"msg": "An unhandled exception occurred while templating '{{ my_list | map(my_dict) | list }}'. Error was a <class 'ValueError'>, original message: key must be a string"}

I want to modify the actual: line so that this playbook runs without error.

Note that I do not want to loop in Ansible itself. This is a simple MWE. In my real example, this lookup should be inline inside a much larger template file.


Solution

  • Given the list

      my_list: [a, c]
    

    Use filter extract

      actual: "{{ my_list|map('extract', my_dict)|list }}"
    

    should give

      actual: [1, 3]
    

    The above solution doesn't work when some items in the list are missing in the dictionary. For example,

      my_list: [a, c, x]
    

    will fail

    ''dict object'' has no attribute ''x'''

    In this case, select the keys that are in the list. The expression below gives the same result

      actual: "{{ my_dict|dict2items|
                          selectattr('key', 'in', my_list)|
                          map(attribute='value')|list }}"
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        my_list: [a, c]
        my_dict:
          a: 1
          b: 2
          c: 3
        actual1: "{{ my_list|map('extract', my_dict)|list }}"
        actual2: "{{ my_dict|dict2items|
                             selectattr('key', 'in', my_list)|
                             map(attribute='value')|list }}"
    
      tasks:
    
        - debug:
            var: actual1|to_yaml
        - debug:
            var: actual2|to_yaml