Ansible map Filter — Transform Lists and Extract Attributes
Introduction
The map filter applies a transformation to every element in a list — extract an attribute, apply another filter, or call a test. It's how you go from a list of dictionaries to a list of specific values, or transform all strings in a list at once.
Extract Attribute from List of Dicts
vars:
users:
- name: alice
uid: 1001
role: admin
- name: bob
uid: 1002
role: developer
- name: carol
uid: 1003
role: developer
tasks:
- name: Get all usernames
ansible.builtin.debug:
msg: "{{ users | map(attribute='name') | list }}"
# Output: ["alice", "bob", "carol"]
- name: Get all UIDs
ansible.builtin.debug:
msg: "{{ users | map(attribute='uid') | list }}"
# Output: [1001, 1002, 1003]
Apply Filter to Each Element
vars:
hostnames:
- Web-Server-01
- DB-Server-01
- Cache-Server-01
tasks:
- name: Convert all to lowercase
ansible.builtin.debug:
msg: "{{ hostnames | map('lower') | list }}"
# Output: ["web-server-01", "db-server-01", "cache-server-01"]
- name: Add prefix to each
ansible.builtin.debug:
msg: "{{ hostnames | map('regex_replace', '^', 'prod-') | list }}"
# Output: ["prod-Web-Server-01", "prod-DB-Server-01", "prod-Cache-Server-01"]
Common map Patterns
Extract Nested Attributes
vars:
servers:
- name: web01
network:
ip: 10.0.1.10
subnet: 10.0.1.0/24
- name: web02
network:
ip: 10.0.1.11
subnet: 10.0.1.0/24
tasks:
- name: Get all IPs (nested attribute)
ansible.builtin.debug:
msg: "{{ servers | map(attribute='network.ip') | list }}"
# Output: ["10.0.1.10", "10.0.1.11"]
Map with int/string Conversion
vars:
port_strings: ["8080", "8443", "9090"]
tasks:
- name: Convert to integers
ansible.builtin.debug:
msg: "{{ port_strings | map('int') | list }}"
# Output: [8080, 8443, 9090]
Map + Join (Build Strings)
vars:
packages:
- nginx
- postgresql
- redis
tasks:
- name: Build comma-separated list
ansible.builtin.debug:
msg: "{{ packages | join(', ') }}"
# Output: "nginx, postgresql, redis"
- name: Build install command
ansible.builtin.debug:
msg: "apt install {{ packages | join(' ') }}"
Map + Select (Filter + Transform)
vars:
users:
- name: alice
active: true
- name: bob
active: false
- name: carol
active: true
tasks:
- name: Get names of active users only
ansible.builtin.debug:
msg: "{{ users | selectattr('active') | map(attribute='name') | list }}"
# Output: ["alice", "carol"]
Map + Flatten
vars:
teams:
- name: platform
members: [alice, bob]
- name: frontend
members: [carol, dave]
tasks:
- name: Get all members across teams
ansible.builtin.debug:
msg: "{{ teams | map(attribute='members') | flatten | list }}"
# Output: ["alice", "bob", "carol", "dave"]
map vs selectattr vs reject
| Filter | Purpose | Example |
|---|---|---|
map | Transform each element | Extract attribute, apply filter |
selectattr | Filter by attribute value | Keep items where active == true |
rejectattr | Exclude by attribute | Remove items where disabled == true |
select | Filter by test | Keep items matching a test |
# Chaining: filter then transform
- debug:
msg: >-
{{ users
| selectattr('role', 'equalto', 'admin')
| map(attribute='name')
| list }}
# Get names of admins only
Real-World Examples
Generate /etc/hosts Entries
- name: Build hosts entries
ansible.builtin.copy:
content: |
{% for entry in groups['all'] | map('extract', hostvars, ['ansible_host']) %}
{{ entry }} {{ groups['all'][loop.index0] }}
{% endfor %}
dest: /etc/hosts.d/ansible-managed
Install Packages from Structured Data
vars:
applications:
- name: webapp
packages: [nginx, nodejs, npm]
- name: database
packages: [postgresql, postgresql-contrib]
tasks:
- name: Install all packages
ansible.builtin.apt:
name: "{{ applications | map(attribute='packages') | flatten }}"
state: present
Build Docker Compose Ports
vars:
services:
- name: web
port: 8080
- name: api
port: 3000
- name: admin
port: 9090
tasks:
- name: Show port mappings
ansible.builtin.debug:
msg: "{{ services | map(attribute='port') | map('string') | map('regex_replace', '^(.*)$', '\\1:\\1') | list }}"
# Output: ["8080:8080", "3000:3000", "9090:9090"]
Extract from Registered Output
- name: Get running containers
ansible.builtin.command: docker ps --format json
register: docker_ps
changed_when: false
- name: Parse container names
ansible.builtin.set_fact:
container_names: "{{ docker_ps.stdout_lines | map('from_json') | map(attribute='Names') | list }}"
Troubleshooting
| Issue | Fix |
|---|---|
map returns generator, not list | Add | list at the end |
undefined attribute error | Use default filter: map(attribute='key', default='') |
| Nested attribute fails | Use dot notation: map(attribute='parent.child') |
| Can't chain map with selectattr | Put selectattr BEFORE map |
| Filter not found in map | Use quotes: map('upper') not map(upper) |
Best Practices
- Always add
| list— map returns a generator, not a list - Chain selectattr → map — filter first, transform second
- Use
defaultfor optional attributes — prevents undefined errors - Prefer
map(attribute=...)over loops — cleaner and faster - Use
flattenafter mapping lists — collapses nested lists - Test complex chains incrementally — debug each step
Conclusion
The map filter transforms lists: extract attributes with map(attribute='key'), apply filters with map('filtername'), or chain with selectattr for filtered transformations. Always end with | list to materialize the result. Combine with join, flatten, and select for powerful one-line data transformations.