Ansible Jinja2 Filters — map, select, reject, json_query
Introduction
Jinja2 filters transform data inline — extracting fields from lists, filtering objects by attributes, reformatting strings, and querying nested JSON structures. Ansible adds dozens of custom filters beyond standard Jinja2. This guide covers the filters you'll use most often with practical examples.
map — Transform Every Item
# Extract attribute from list of dicts
- name: Get all hostnames
ansible.builtin.debug:
msg: "{{ users | map(attribute='name') | list }}"
vars:
users:
- { name: alice, role: admin }
- { name: bob, role: user }
# Output: ["alice", "bob"]
# Apply filter to every item
- name: Uppercase all items
ansible.builtin.debug:
msg: "{{ fruits | map('upper') | list }}"
vars:
fruits: [apple, banana, cherry]
# Output: ["APPLE", "BANANA", "CHERRY"]
# Apply filter with argument
- name: Prefix all items
ansible.builtin.debug:
msg: "{{ names | map('regex_replace', '^', 'user_') | list }}"
vars:
names: [alice, bob, charlie]
# Output: ["user_alice", "user_bob", "user_charlie"]
select / reject — Filter Items
# Select items matching a test
- name: Get even numbers
ansible.builtin.debug:
msg: "{{ numbers | select('even') | list }}"
vars:
numbers: [1, 2, 3, 4, 5, 6]
# Output: [2, 4, 6]
# Reject items matching a test
- name: Remove empty strings
ansible.builtin.debug:
msg: "{{ items | reject('equalto', '') | list }}"
vars:
items: ["hello", "", "world", "", "!"]
# Output: ["hello", "world", "!"]
# Select strings matching pattern
- name: Get web servers
ansible.builtin.debug:
msg: "{{ servers | select('match', 'web.*') | list }}"
vars:
servers: [web01, db01, web02, cache01, web03]
# Output: ["web01", "web02", "web03"]
# Select numbers greater than
- name: High values
ansible.builtin.debug:
msg: "{{ values | select('gt', 100) | list }}"
vars:
values: [50, 150, 75, 200, 30]
# Output: [150, 200]
selectattr / rejectattr — Filter by Attribute
# Select dicts where attribute matches
- name: Get active users
ansible.builtin.debug:
msg: "{{ users | selectattr('active', 'equalto', true) | list }}"
vars:
users:
- { name: alice, active: true }
- { name: bob, active: false }
- { name: charlie, active: true }
# Output: [{"name": "alice", "active": true}, {"name": "charlie", "active": true}]
# Chain selectattr + map
- name: Get names of admin users
ansible.builtin.debug:
msg: "{{ users | selectattr('role', 'equalto', 'admin') | map(attribute='name') | list }}"
vars:
users:
- { name: alice, role: admin }
- { name: bob, role: user }
- { name: charlie, role: admin }
# Output: ["alice", "charlie"]
# Reject by attribute
- name: Get non-disabled services
ansible.builtin.debug:
msg: "{{ services | rejectattr('disabled', 'defined') | map(attribute='name') | list }}"
json_query (JMESPath)
# Query nested structures
- name: Extract from complex JSON
ansible.builtin.debug:
msg: "{{ data | json_query('servers[*].name') }}"
vars:
data:
servers:
- { name: web01, region: us-east, type: t3.micro }
- { name: web02, region: eu-west, type: t3.small }
- { name: db01, region: us-east, type: r6g.large }
# Filter with conditions
- name: US East servers only
ansible.builtin.debug:
msg: "{{ data | json_query(\"servers[?region=='us-east'].name\") }}"
# Output: ["web01", "db01"]
# Multiple fields
- name: Name and type pairs
ansible.builtin.debug:
msg: "{{ data | json_query('servers[*].{host: name, size: type}') }}"
# Nested query
- name: Get specific value
ansible.builtin.debug:
msg: "{{ api_response | json_query('data.items[0].metadata.name') }}"
String Filters
# regex_replace
- ansible.builtin.debug:
msg: "{{ 'hello-world_v2' | regex_replace('[^a-zA-Z0-9]', '_') }}"
# Output: "hello_world_v2"
# regex_search (extract match)
- ansible.builtin.debug:
msg: "{{ version_string | regex_search('[0-9]+\\.[0-9]+\\.[0-9]+') }}"
vars:
version_string: "app-v2.5.1-release"
# Output: "2.5.1"
# split and join
- ansible.builtin.debug:
msg: "{{ 'a,b,c' | split(',') | join(' | ') }}"
# Output: "a | b | c"
# hash
- ansible.builtin.debug:
msg: "{{ 'my_password' | hash('sha256') }}"
Collection Filters
# combine (merge dicts)
- ansible.builtin.set_fact:
full_config: "{{ defaults | combine(overrides, recursive=True) }}"
vars:
defaults:
app: { port: 8080, workers: 4 }
db: { host: localhost }
overrides:
app: { port: 9090 }
db: { host: db.example.com }
# Result: {app: {port: 9090, workers: 4}, db: {host: "db.example.com"}}
# unique
- ansible.builtin.debug:
msg: "{{ [1, 2, 2, 3, 3, 3] | unique | list }}"
# Output: [1, 2, 3]
# flatten
- ansible.builtin.debug:
msg: "{{ [[1, 2], [3, 4], [5]] | flatten }}"
# Output: [1, 2, 3, 4, 5]
# difference / intersect / union
- ansible.builtin.debug:
msg: |
Diff: {{ [1,2,3] | difference([2,3,4]) }}
Inter: {{ [1,2,3] | intersect([2,3,4]) }}
Union: {{ [1,2,3] | union([2,3,4]) }}
# Diff: [1], Inter: [2, 3], Union: [1, 2, 3, 4]
# dict2items / items2dict
- ansible.builtin.debug:
msg: "{{ my_dict | dict2items }}"
vars:
my_dict: { a: 1, b: 2 }
# Output: [{"key": "a", "value": 1}, {"key": "b", "value": 2}]
Type Conversion
# Type casting
"{{ '42' | int }}" # → 42
"{{ '3.14' | float }}" # → 3.14
"{{ 'yes' | bool }}" # → true
"{{ [1, 2] | string }}" # → "[1, 2]"
"{{ data | to_json }}" # → JSON string
"{{ json_str | from_json }}" # → dict/list
"{{ data | to_yaml }}" # → YAML string
"{{ yaml_str | from_yaml }}" # → dict/list
Chaining Filters (Real Example)
# Get IPs of running web containers
- name: Complex filter chain
ansible.builtin.debug:
msg: >-
{{ containers
| selectattr('state', 'equalto', 'running')
| selectattr('name', 'match', 'web-.*')
| map(attribute='network')
| map(attribute='ip')
| list
| join(', ') }}
Troubleshooting
| Issue | Solution |
|---|---|
list not found | Add | list after map/select (they return generators) |
json_query not found | Install pip install jmespath |
| Regex backslash issues | Double-escape in YAML: \\d or use raw strings |
| Type error in comparison | Cast first: | int before comparing |
| Empty result | Check attribute names; use default([]) |
Best Practices
- Always
| list— aftermap,select,reject(they return generators) - Use
selectattr+map— filter then extract in one chain - Prefer
json_query— for deeply nested structures - Use
default()— prevent undefined variable errors - Keep chains readable — break long chains across lines with
>- - Test in debug — verify filter chains with
debugbefore using in tasks
Conclusion
Jinja2 filters are Ansible's data manipulation toolkit. map and selectattr replace verbose loops, json_query navigates complex API responses, and combine merges configuration layers. Master these filters and you'll write playbooks that handle complex data transformations in single expressions instead of multi-task workarounds.