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'] }}"AliceBracket 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 | list — keys() 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 }}"2Converting 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=Truewithcombineby 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/items2dictwhen 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.