Search code examples
pythonansiblejinja2devops

Ansible - join / flatten a dict of lists


I have a dict of lists such as here, though the inner data could be any level of complexity (perhaps strings, perhaps dicts, perhaps multi layer nested complex object).

my_dict:
  my_list_a:
    - a
    - b
    - c
  my_list_b:
    - a
    - d
    - e
  list_c:
    - f
  1. How do I flatten this structure / join the inner items into a list in the following format through jinja2 (preserving order is not important to me in my current use case but it's always nice when possible).
my_combined_list:
  - a
  - b
  - c
  - a
  - d
  - e
  - f

Unfortunately as list comprehensions are out of the question due to lack of support in Jinja, I haven't been able to piece a solution together so far.

There are also some extra things I need to do, though most are easily doable on the final list (deduplicate, sort, etc). Something that needs to be done during the join is:

  1. select or reject based on a test (e.g. only include lists that start with string: my_ meaning list_c is not added to the end result)

I've found many ways for list of dicts, or various more complex special cases, but I can't seem to find anything on making a list of all inner items of a dict of lists of items.


Solution

  • Q: Flatten the inner items into a list.

    A: The simplest option is

    my_combined_list: "{{ my_dict.values() | flatten }}"
    

    gives

    my_combined_list: [a, b, c, a, d, e, f]
    

    Q: Select or reject based on a test (start string: my_ , e.g. list_c is omitted).

    A: For example, use the test match

    allow_match: [my_]
    my_combined_list: "{{ my_dict | dict2items |
                          selectattr('key', 'match', allow_match|join('|')) |
                          map(attribute='value') | flatten }}"
    

    gives

    my_combined_list: [a, b, c, a, d, e]
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        my_dict:
          my_list_a: [a, b, c]
          my_list_b: [a, d, e]
          list_c: [f]
    
        my_combined_list: "{{ my_dict.values() | flatten }}"
        allow_match: [my_]
        my_combined_lis2: "{{ my_dict | dict2items |
                              selectattr('key', 'match', allow_match|join('|')) |
                              map(attribute='value') | flatten }}"
    
      tasks:
    
        - debug:
            var: my_combined_list | to_yaml
        - debug:
            var: my_combined_lis2 | to_yaml