Removing a deeply-nested dict item in Ansible
I searched for an answer to this question for at least an hour and surprisingly couldn’t find one!
Suppose you are writing an Ansible playbook, and you have a complex dictionary (with deeply nested dictionaries inside it) and you need to remove an item from one of the nested dictionaries.
Jeff Geerling has a nice blog here that tells you how to add or modify an item in a dictionary that is deeply nested, but it says nothing about how you can remove an item from a dictionary that is deeply nested. This is what I will now explain. I cannot accept credit for this solution — all credit goes to Chris Francy (aka zoredache) who proposed this solution while helping me in the #ansible IRC chat room.
In this example, I want to remove a remove: "me"
item from the full_dict.outer_dict.inner_dict
dictionary:
full_dict:
outer_key: "outer key value"
outer_dict:
inner_key: "inner key value"
inner_dict:
foo: "bar"
remove: "me"
To do this, I need to first build a dictionary with only the items I want to keep in that inner dictionary. So I have this:
new_inner_dict:
foo: "bar"
In other words, my new_inner_dict
is exactly how I want my full_dict.outer_dict.inner_dict
to look like — it has all the items I want without all the items I do not want.
At this point, I need to do a little Ansible magic to pop off the original inner_dict
so it is completely removed. Now I can use combine()
to add a new inner_dict
to my full_dict.outer_dict
. This is how you do this:
set_fact:
full_dict: |
{% set a=full_dict['outer_dict'].pop('inner_dict') %}
{{ full_dict | combine({'outer_dict': {'inner_dict': new_inner_dict}}, recursive=True) }}
At this point, my full_dict
looks exactly how I want it — specifically, the remove: "me"
item has now been removed from full_dict.outer_dict.inner_dict
:
full_dict:
outer_key: "outer key value"
outer_dict:
inner_key: "inner key value"
inner_dict:
foo: "bar"
Here’s a full Ansible playbook you can run to see it in action — store this file as ansible-test.yml
and run ansible-playbook ansible-test.yml
.
- hosts: localhost
gather_facts: false
tasks:
- name: "Set the test facts"
set_fact:
new_inner_dict:
foo: "bar"
full_dict:
outer_key: "outer key value"
outer_dict:
inner_key: "inner key value"
inner_dict:
foo: "bar"
remove: "me" - name: "This is the full dictionary"
debug:
var: full_dict - name: "Use 'combine' to set inner_dict to a dictionary that does not have 'remove: me' item"
set_fact:
full_dict: "{{ full_dict | combine({'outer_dict': {'inner_dict': new_inner_dict}}, recursive=True) }}" - name: "You would think the inner_dict had its 'remove: me' element removed, but notice it did not"
debug:
var: full_dict - name: "Remove the entire inner_dict object and then combine with the new_inner_dict"
set_fact:
full_dict: |
{% set a=full_dict['outer_dict'].pop('inner_dict') %}
{{ full_dict | combine({'outer_dict': {'inner_dict': new_inner_dict}}, recursive=True) }} - name: "Now notice the 'remove: me' item is gone from the inner_dict"
debug:
var: full_dict