Ansible dict2items Filter: Convert Dictionaries to Lists (Guide)
By Luca Berton · Published 2024-01-01 · Category: database-automation
How to convert dictionaries to lists with Ansible dict2items filter and back with items2dict. Loop over dict key-value pairs.
Ansible's dict2items converts a dictionary into a list of key-value pairs you can loop over. items2dict does the reverse — turns a list back into a dictionary. Together they let you transform, filter, and iterate over data structures cleanly.
dict2items: Dictionary → List
vars:
users:
alice: admin
bob: developer
carol: viewer
tasks:
- name: Show conversion
ansible.builtin.debug:
msg: "{{ users | dict2items }}"
# Result:
# [
# {"key": "alice", "value": "admin"},
# {"key": "bob", "value": "developer"},
# {"key": "carol", "value": "viewer"}
# ]
See also: Ansible combine Filter: Merge Dictionaries & Override Defaults (Guide)
Loop Over a Dictionary
The primary use case — dictionaries aren't directly iterable in Ansible loops:
vars:
packages:
nginx: present
postgresql: present
cups: absent
tasks:
- name: Manage packages
ansible.builtin.package:
name: "{{ item.key }}"
state: "{{ item.value }}"
loop: "{{ packages | dict2items }}"
items2dict: List → Dictionary
vars:
user_list:
- key: alice
value: admin
- key: bob
value: developer
tasks:
- name: Convert to dictionary
ansible.builtin.set_fact:
user_dict: "{{ user_list | items2dict }}"
# Result: {"alice": "admin", "bob": "developer"}
See also: Ansible regex_search & regex_replace Filters: Pattern Matching Guide
Custom Key/Value Names
When your data uses different field names:
vars:
servers:
- hostname: web01
ip: 10.0.1.50
- hostname: web02
ip: 10.0.1.51
tasks:
- name: Convert with custom keys
ansible.builtin.set_fact:
server_map: "{{ servers | items2dict(key_name='hostname', value_name='ip') }}"
# Result: {"web01": "10.0.1.50", "web02": "10.0.1.51"}
Same for dict2items:
vars:
config:
database_host: localhost
database_port: 5432
tasks:
- name: Convert with custom field names
ansible.builtin.debug:
msg: "{{ config | dict2items(key_name='param', value_name='setting') }}"
# [{"param": "database_host", "setting": "localhost"}, ...]
Filter and Transform Dictionaries
Remove Entries
vars:
all_services:
nginx: enabled
cups: disabled
ssh: enabled
bluetooth: disabled
tasks:
- name: Get only enabled services
ansible.builtin.set_fact:
enabled_services: >-
{{ all_services | dict2items
| selectattr('value', 'equalto', 'enabled')
| items2dict }}
# Result: {"nginx": "enabled", "ssh": "enabled"}
Transform Values
vars:
ports:
http: "80"
https: "443"
ssh: "22"
tasks:
# Open firewall for all ports
- name: Open ports
ansible.builtin.iptables:
chain: INPUT
protocol: tcp
destination_port: "{{ item.value }}"
jump: ACCEPT
comment: "Allow {{ item.key }}"
loop: "{{ ports | dict2items }}"
See also: Ansible ternary Filter: Inline If-Else Conditional in Jinja2 (Guide)
Nested Dictionaries
vars:
environments:
production:
host: prod-db.example.com
port: 5432
replicas: 3
staging:
host: staging-db.example.com
port: 5432
replicas: 1
tasks:
- name: Configure each environment
ansible.builtin.template:
src: db-config.j2
dest: "/etc/myapp/db-{{ item.key }}.conf"
loop: "{{ environments | dict2items }}"
# item.key = "production", item.value.host = "prod-db.example.com"
Merge Dictionaries
vars:
defaults:
port: 8080
debug: false
workers: 4
overrides:
port: 9090
debug: true
tasks:
- name: Merge configs (overrides win)
ansible.builtin.set_fact:
final_config: "{{ defaults | combine(overrides) }}"
# Result: {"port": 9090, "debug": true, "workers": 4}
Real-World Examples
Create Users from Dictionary
vars:
team:
alice:
uid: 1001
groups: ['wheel', 'docker']
shell: /bin/bash
bob:
uid: 1002
groups: ['docker']
shell: /bin/zsh
tasks:
- name: Create team members
ansible.builtin.user:
name: "{{ item.key }}"
uid: "{{ item.value.uid }}"
groups: "{{ item.value.groups }}"
shell: "{{ item.value.shell }}"
loop: "{{ team | dict2items }}"
Configure DNS Records
vars:
dns_records:
web01.example.com: 10.0.1.50
web02.example.com: 10.0.1.51
db01.example.com: 10.0.2.10
tasks:
- name: Add DNS entries to /etc/hosts
ansible.builtin.lineinfile:
path: /etc/hosts
line: "{{ item.value }} {{ item.key }}"
regexp: ".*{{ item.key }}$"
loop: "{{ dns_records | dict2items }}"
Environment Variables from Dictionary
vars:
app_env:
DATABASE_URL: "postgresql://localhost/myapp"
REDIS_URL: "redis://localhost:6379"
SECRET_KEY: "{{ vault_secret_key }}"
tasks:
- name: Write .env file
ansible.builtin.copy:
content: |
{% for item in app_env | dict2items %}
{{ item.key }}={{ item.value }}
{% endfor %}
dest: /opt/myapp/.env
mode: '0600'
FAQ
When should I use dict2items vs just looping?
You need dict2items because Ansible's loop expects a list. Dictionaries can't be looped directly. dict2items converts {a: 1, b: 2} into [{key: a, value: 1}, {key: b, value: 2}] which loop can iterate.
Can I sort dict2items output?
Yes: {{ my_dict | dict2items | sort(attribute='key') }}. Dictionaries in Python 3.7+ preserve insertion order, but sorting ensures consistent ordering.
What's the difference between dict2items and the Jinja2 items() method?
dict2items returns a list of {key, value} objects. Jinja2's items() returns tuples. In Ansible, dict2items is preferred because it produces proper objects with named attributes.
How do I filter a dictionary by key pattern?
# Keep only keys starting with 'db_'
{{ my_dict | dict2items
| selectattr('key', 'match', '^db_')
| items2dict }}
Conclusion
dict2items and items2dict are the bridge between Ansible's dictionary data and its list-based looping. Use dict2items to iterate over dictionaries, selectattr to filter, and items2dict to rebuild. Together with combine for merging, they handle every dictionary transformation pattern.
Related Articles
• Ansible map vs selectattr vs json_query • Ansible set_fact vs vars vs extra_vars • Ansible ternary Filter Guide • Ansible Jinja2 Templates GuideCategory: database-automation