David Resnick
David Resnick

Reputation: 4981

How to remove a single key from an Ansible dictionary?

I'd like to remove a single key from a dictionary in Ansible.

For example, I'd like this:

- debug: var=dict2
  vars:
    dict:
      a: 1
      b: 2
      c: 3
    dict2: "{{ dict | filter_to_remove_key('a') }}"

To print this:

ok: [localhost] => {
    "dict2": {
        "b": 2,
        "c": 3
    }
}

Please note that the dictionary is loaded from a json file and I POST it to the Grafana REST API. I'd like to allow saving an 'id' key in the file and remove the key before POSTing it.

This is closer to the actual use I have for the removal:

- name: Install Dashboards   
  uri:
    url: "{{ grafana_api_url }}/dashboards/db"
    method: POST
    headers:
      Authorization: Bearer {{ grafana_api_token }}
    body:
      overwrite: true
      dashboard:
        "{{ lookup('file', item) | from_json | removekey('id') }}"
    body_format: json   with_fileglob:
    - "dashboards/*.json"
    - "../../../dashboards/*.json"

Upvotes: 20

Views: 26734

Answers (7)

Tret
Tret

Reputation: 1

Another possible solution to this without using filter and still only deleting on the first layer (or a controllable layer depth) is using dict2items and items2dict

- set_fact:
    temp:
      a: 1
      b:
        c: 2
      d:
        - 3
- debug:
    var: temp | dict2items | selectattr('key', 'ne', 'b') | items2dict

This will result in:

a: 1
d:
  - 3

With this selectattr you could also define better checks for which keys to delete.

Upvotes: 0

stackprotector
stackprotector

Reputation: 13412

You can use the ansible.utils.remove_keys filter. It will remove the given key(s) on all nesting layers. So it might not be suitable in every scenario:

- debug: var=dict2
  vars:
    dict:
      a: 1
      b: 2
      c: 3
    dict2: "{{ dict | ansible.utils.remove_keys(target=['a']) }}"

Output:

ok: [localhost] => 
  dict2:
    b: 2
    c: 3

If you want to remove a particular key on a particular layer, you can pop that key away like this:

- debug: var=dict2
  vars:
    dict:
      a: 1
      b: 2
      c: 3
    dict2: |
      {% set a = dict.pop('a') %}
      {{ dict }}

Example with deeper nesting:

- debug: var=dict2
  vars:
    dict:
      sub_dict:
        a: 1
        b: 2
        c: 3
    dict2: |
      {% set a = dict.sub_dict.pop('a') %}
      {{ dict }}

Upvotes: 7

djorem
djorem

Reputation: 91

You can accomplish this by using combine and omit

- set_fact:
    dict:
      a: 1
      b: 2
      c: 3

- debug:
    var: dict

- debug:
    msg: "{{ dict | combine({ 'a': omit }) }}"
TASK [test : set_fact] 
ok: [server]

TASK [test: debug] 
ok: [server] => {
    "dict": {
        "a": 1,
        "b": 2,
        "c": 3
    }
}

TASK [test : debug] 
ok: [server] => {
    "msg": {
        "b": 2,
        "c": 3
    }
}

Upvotes: 9

Sergei
Sergei

Reputation: 61

  - debug: var=dict2
    vars:
      dict:
        a: 1
        b: 2
        c: 3
      dict2: '{{ dict | dict2items | rejectattr("key", "eq", "a") | list | items2dict }}'
      #dict2: '{{ dict | dict2items | rejectattr("key", "match", "^(a|b)$") | list | items2dict }}'

Output:

ok: [localhost] => {
    "dict2": {
        "b": 2,
        "c": 3
    }
}

Upvotes: 6

mallniya
mallniya

Reputation: 23

If you interested in filter (which is in my opinion the cleanest way to delete an item in dict) then create filter_plugins/dicts.py in directory, which your playbook resides in, and fill it with:

'''Custom ansible filters for dicts'''

import copy


class FilterModule(object):

    def filters(self):
        return {
            'del_by_list': self.del_by_list
        }

    def del_by_list(self, dict_to_reduce, list_of_keys):
        '''Deletes items of dict by list of keys provided'''
        dict_to_return = copy.deepcopy(dict_to_reduce)
        for item in list_of_keys:
            if item in dict_to_return:
                del dict_to_return[item]
        return dict_to_return

And you good to go:

---
- hosts: hadoop
  gather_facts: no
  tasks:

    - debug:
        msg: "{{ {123: 456, 789: 123} | del_by_list([123]) }}"

This will yield {789: 123}

Upvotes: 2

William Price
William Price

Reputation: 4102

Here's an approach inspired by an article by John Mazzitelli that can be used inline without additional set_fact tasks, etc.:

Task:

tasks:
- debug: var=dict2
  vars:
    dict:
      a: 1
      b: 2
      c: 3
    # It is important that there be NO WHITESPACE outside of `{% ... %}` and `{{ ... }}`
    # or else the var will be converted to a string.  The copy() step optionally prevents
    # modifying the original. If you don't care, then: "{% set removed=dict.pop('a') %}{{dict}}"
    dict2: "{% set copy=dict.copy() %}{% set removed=copy.pop('a') %}{{ copy }}"

Outputs:

TASK [debug] ***********
ok: [localhost] => {
    "dict2": {
        "b": 2,
        "c": 3
    }
}

Upvotes: 5

fumiyas
fumiyas

Reputation: 367

- set_fact:
    dict:
      a: 1
      b: 2
      c: 3
    dict2: {}

- set_fact:
    dict2: "{{dict2 |combine({item.key: item.value})}}"
  when: "{{item.key not in ['a']}}"
  with_dict: "{{dict}}"

- debug: var=dict2

or create a filter plugin and use it.

Upvotes: 14

Related Questions