Skip to content

Working With Dictionaries


Dictionaries have already shown up constantly in this course — ansible_facts is one, a registered task’s result is one. This chapter covers working with them directly: accessing keys, converting between dicts and lists, and merging two dictionaries together — including a gotcha genuinely capable of silently losing data if you’re not aware of it.

Defining And Accessing A Dictionary

vars:
  person:
    name: Alice
    age: 30
tasks:
  - name: Print the name
    ansible.builtin.debug:
      msg: "{{ person['name'] }}"
Alice

Bracket notation here for the same reason it was recommended back in the facts chapter — reliable, unambiguous access to any key, regardless of what it’s named.

Getting Keys And Values

msg: "{{ person.keys() | list }}"
['name', 'age']
msg: "{{ person.values() | list }}"
['Alice', 30]

Both need the trailing | listkeys() and values() hand back something that needs to be explicitly turned into a real list before it prints the way you’d expect, the same requirement zip had in the previous chapter.

length works here too, recall from Chapter 1 — on a dictionary, it’s the number of keys:

msg: "{{ person | length }}"
2

Converting Between Dicts And Lists: dict2items And items2dict

dict2items turns a dictionary into a list of key/value pairs:

msg: "{{ person | dict2items }}"
[{"key": "name", "value": "Alice"}, {"key": "age", "value": 30}]

This is genuinely useful once you’re looping over a dictionary’s contents (loops themselves come later in this course), since a list of key/value pairs is often exactly the shape a loop construct expects. items2dict does the reverse:

vars:
  person_items:
    - key: name
      value: Alice
    - key: age
      value: 30
tasks:
  - name: Convert back to a dict
    ansible.builtin.debug:
      msg: "{{ person_items | items2dict }}"
{"name": "Alice", "age": 30}

Merging Dictionaries With combine

combine merges two dictionaries, with the second one’s keys winning on conflict:

vars:
  defaults:
    timeout: 30
    retries: 3
  overrides:
    timeout: 60
tasks:
  - name: Merge
    ansible.builtin.debug:
      msg: "{{ defaults | combine(overrides) }}"
{"timeout": 60, "retries": 3}

timeout took the override’s value; retries, absent from overrides, came through untouched from defaults. This exact pattern — a base set of values, merged with a smaller set of overrides — is precisely how you’ll build sensible defaults later in this section.

A Gotcha: combine Doesn’t Merge Nested Dicts By Default

Here’s the dangerous part. The moment a key’s value is itself a dictionary, combine’s default behavior isn’t to merge that nested dictionary’s keys — it replaces the whole thing wholesale:

vars:
  defaults:
    database:
      host: localhost
      port: 5432
  overrides:
    database:
      port: 5433
tasks:
  - name: Naive merge
    ansible.builtin.debug:
      msg: "{{ defaults | combine(overrides) }}"
{"database": {"port": 5433}}

host is simply gone — not overridden, not preserved, just entirely absent. combine, by default, treated overrides['database'] as a complete replacement for defaults['database'], rather than merging the two nested dictionaries’ keys together.

The fix is recursive=True:

    - name: Recursive merge
      ansible.builtin.debug:
        msg: "{{ defaults | combine(overrides, recursive=True) }}"
{"database": {"host": "localhost", "port": 5433}}

With recursive=True, combine walks into nested dictionaries and merges their keys individually, rather than swapping the whole nested value out. Any time you’re merging configuration-like structures with nested dictionaries — which is most of the time you’ll reach for combine at all — recursive=True is very likely what you actually want, and forgetting it is an easy way to silently lose data without any error telling you it happened.

Best Practices

  • Use recursive=True with combine by default whenever either dictionary might contain nested dictionaries — treat leaving it off as a deliberate choice for a genuinely flat merge, not a default you forgot to add.
  • Use bracket notation for dictionary access, consistent with the same recommendation for facts.
  • Reach for dict2items/items2dict when you need to convert between a dictionary’s shape and a list of key/value pairs — especially useful once loops enter the picture later in this course.
Last updated on