AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible map vs selectattr vs json_query: Filter Data the Right Way

By Luca Berton · Published 2024-01-01 · Category: installation

Complete comparison of Ansible map, selectattr, and json_query filters with practical examples. Learn when to use each for transforming lists, filtering.

Ansible offers three main approaches for filtering and transforming data: map, selectattr, and json_query. They solve different problems, and picking the wrong one creates unreadable playbooks. This guide shows when and how to use each.

Quick Comparison

| Filter | Purpose | Input | Syntax | |--------|---------|-------|--------| | map | Transform each item in a list | Lists | Jinja2 native | | selectattr | Filter list of dicts by attribute | List of dicts | Jinja2 native | | json_query | Query nested JSON structures | Any JSON | JMESPath (requires jmespath pip package) |

See also: Ansible select & selectattr Filters: Filter Lists by Condition (Guide)

map: Transform Every Item

map applies a filter or extracts an attribute from every item in a list.

Extract Attribute from List of Dicts

- vars:
    users:
      - { name: alice, role: admin }
      - { name: bob, role: developer }
      - { name: carol, role: admin }

tasks: - name: Get all usernames ansible.builtin.debug: msg: "{{ users | map(attribute='name') | list }}" # Result: ['alice', 'bob', 'carol']

Apply Filter to Each Item

- name: Uppercase all names
  ansible.builtin.debug:
    msg: "{{ users | map(attribute='name') | map('upper') | list }}"
  # Result: ['ALICE', 'BOB', 'CAROL']

- name: Add prefix to each item ansible.builtin.debug: msg: "{{ packages | map('regex_replace', '^(.*)$', 'python3-\\1') | list }}" vars: packages: [requests, flask, celery] # Result: ['python3-requests', 'python3-flask', 'python3-celery']

map with format

- name: Format each item
  ansible.builtin.debug:
    msg: "{{ ports | map('string') | map('regex_replace', '^(.*)$', '-A INPUT -p tcp --dport \\1 -j ACCEPT') | list }}"
  vars:
    ports: [80, 443, 8080]

When to Use map

• Extracting one field from a list of objects • Applying a transformation to every item • Building new lists from existing data • Chaining with other filters (| map(...) | select(...) | list)

selectattr: Filter by Attribute

selectattr keeps only items from a list of dictionaries where an attribute matches a condition.

Basic Filtering

- vars:
    users:
      - { name: alice, role: admin, active: true }
      - { name: bob, role: developer, active: false }
      - { name: carol, role: admin, active: true }
      - { name: dave, role: developer, active: true }

tasks: - name: Get active admins ansible.builtin.debug: msg: "{{ users | selectattr('role', 'equalto', 'admin') | selectattr('active') | list }}" # Result: [{ name: alice, role: admin, active: true }, # { name: carol, role: admin, active: true }]

Available Tests

# Equal to
{{ users | selectattr('role', 'equalto', 'admin') | list }}

# Not equal {{ users | rejectattr('role', 'equalto', 'admin') | list }}

# Contains (string) {{ servers | selectattr('name', 'contains', 'web') | list }}

# Match (regex) {{ servers | selectattr('name', 'match', '^web-\d+$') | list }}

# Defined {{ servers | selectattr('backup_enabled', 'defined') | list }}

# Boolean (truthy) {{ users | selectattr('active') | list }}

# Greater than (Ansible 2.11+) {{ servers | selectattr('cpu_count', 'greaterthan', 4) | list }}

Chaining selectattr with map

- name: Get names of active admins
  ansible.builtin.debug:
    msg: >-
      {{ users
         | selectattr('role', 'equalto', 'admin')
         | selectattr('active')
         | map(attribute='name')
         | list }}
  # Result: ['alice', 'carol']

When to Use selectattr

• Filtering a list of dicts by one or more attributes • "Give me all items where X equals Y" • Replacing loop + when with a filter expression • Building filtered lists for other tasks

selectattr vs loop + when

# ❌ Works but verbose — loop with conditional
- name: Process active admins
  ansible.builtin.user:
    name: "{{ item.name }}"
    groups: wheel
  loop: "{{ users }}"
  when: item.role == 'admin' and item.active

# ✅ Cleaner — pre-filter the list - name: Process active admins ansible.builtin.user: name: "{{ item.name }}" groups: wheel loop: "{{ users | selectattr('role', 'equalto', 'admin') | selectattr('active') | list }}"

See also: Ansible Jinja2 Filters: Transform Data in Playbooks (Complete Reference)

json_query: Query Nested JSON

json_query uses JMESPath syntax for complex nested JSON queries. It's the most powerful option but requires the jmespath Python package.

Installation

pip install jmespath
# Or in requirements.txt: jmespath>=1.0

Basic Query

- vars:
    api_response:
      data:
        servers:
          - { name: web-1, region: us-east-1, state: running, type: t3.medium }
          - { name: web-2, region: us-east-1, state: stopped, type: t3.medium }
          - { name: db-1, region: us-west-2, state: running, type: r5.large }
          - { name: web-3, region: eu-west-1, state: running, type: t3.small }

tasks: - name: Get all server names ansible.builtin.debug: msg: "{{ api_response | json_query('data.servers[*].name') }}" # Result: ['web-1', 'web-2', 'db-1', 'web-3']

- name: Get running servers ansible.builtin.debug: msg: "{{ api_response | json_query('data.servers[?state==`running`].name') }}" # Result: ['web-1', 'db-1', 'web-3']

- name: Get us-east servers ansible.builtin.debug: msg: "{{ api_response | json_query('data.servers[?region==`us-east-1`]') }}"

Complex Queries

    - name: Multi-condition filter
      ansible.builtin.debug:
        msg: "{{ api_response | json_query('data.servers[?state==`running` && region==`us-east-1`].name') }}"
      # Result: ['web-1']

- name: Select specific fields ansible.builtin.debug: msg: "{{ api_response | json_query('data.servers[*].{server_name: name, server_region: region}') }}" # Result: [{server_name: web-1, server_region: us-east-1}, ...]

- name: Sort results ansible.builtin.debug: msg: "{{ api_response | json_query('sort_by(data.servers, &name)[*].name') }}"

- name: Count results ansible.builtin.debug: msg: "{{ api_response | json_query('length(data.servers[?state==`running`])') }}" # Result: 3

When to Use json_query

• Deeply nested JSON (3+ levels) • Complex multi-condition filtering • API responses with irregular structure • When you need JMESPath functions (sort_by, max_by, contains, etc.) • Selecting/renaming specific fields from objects

Side-by-Side Comparison

Task: Get names of running servers

# Using map + select (Jinja2 native)
{{ servers | selectattr('state', 'equalto', 'running') | map(attribute='name') | list }}

# Using json_query (JMESPath) {{ servers | json_query('[?state==`running`].name') }}

Task: Get servers in us-east with > 4 CPUs

# Using selectattr chain
{{ servers
   | selectattr('region', 'equalto', 'us-east-1')
   | selectattr('cpus', 'greaterthan', 4)
   | list }}

# Using json_query {{ servers | json_query('[?region==`us-east-1` && cpus > `4`]') }}

Task: Extract nested field

# Using map — doesn't work well with nested fields
# ❌ {{ items | map(attribute='config.database.host') | list }}  # may fail

# Using json_query — handles nesting natively {{ items | json_query('[*].config.database.host') }}

See also: Ansible from_json & to_json Filters: Parse & Generate JSON Data (Guide)

Decision Guide

Is your data a simple list of dicts (1 level deep)?
├── YES → Do you need to filter by attribute?
│   ├── YES → selectattr
│   └── NO  → Do you need to transform items?
│       ├── YES → map
│       └── NO  → Just use the list directly
└── NO → Is it deeply nested JSON (3+ levels)?
    ├── YES → json_query
    └── NO  → Try selectattr + map first, fall back to json_query

FAQ

Do I need to install jmespath for json_query?

Yes. json_query requires the jmespath Python package on the Ansible controller. Install with pip install jmespath. The map and selectattr filters are built into Jinja2 and require no additional packages.

Which filter is fastest?

map and selectattr are Jinja2-native and slightly faster for simple operations. json_query has overhead from JMESPath compilation but handles complex queries more efficiently than chaining multiple Jinja2 filters.

Can I chain map and selectattr?

Yes — this is the recommended pattern. Filter first with selectattr, then transform with map: {{ items | selectattr('active') | map(attribute='name') | list }}.

Why does my json_query return None?

Common causes: backtick quoting (JMESPath uses backticks for literals, not single quotes), wrong path depth, or the jmespath package is not installed. Test queries at first.

Conclusion

Use map to transform lists, selectattr to filter by attributes, and json_query for complex nested JSON. For most Ansible work, selectattr + map covers 90% of needs without external dependencies. Reserve json_query for deeply nested API responses.

Related Articles

Ansible map Filter GuideAnsible Jinja2 Filters Complete ReferenceAnsible selectattr Filter GuideAnsible register: Save Task Output

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home