0

I have a dictionary with key values and few values are lists. I want to delete an item from the list inside the dictionary. Dict looks like below which has nested items.

Dict Looks Like:

title: Some Title
metadata : 
   manifest-version: 1.0
   key: value
installers:
   - name: someName1
     version: 1.0
   - name: someName2
     version: 2.0
   - name: someName3
     packages:
       - fileName: fileName1
         version: 1.1.1
       - fileName: fileName2
         version: 2.2.2
   - name: service
     type: install
     packages:
       - name: serviceName1
         manifest: someManifest2
         source: abcd.tgz
         files:
           - file1.tar
           - file2.tar
       - name: serviceName2
         manifest: someManifest2
         source: efgh.tgz
         files:
           - file3.tar
           - file4.tar
       - name: serviceName3
         manifest: someManifest3
         source: ijkl.tgz
         files:
           - file5.tar
           - file6.tar

How do I delete item which has name: serviceName3? or Can I copy the dict to another dict without the below item?

The final dict should not contain the below item:

- name: serviceName2
  manifest: someManifest2
  source: efgh.tgz
  files:
    - file3.tar
    - file4.tar
saeed foroughi
  • 1,662
  • 1
  • 13
  • 25
Arjun Kanti
  • 101
  • 2
  • 4
  • 12
  • `del dict[key][key]...` – xiaoyu2006 Jan 24 '20 at 17:29
  • 2
    What have you tried so far? – Ed Ward Jan 24 '20 at 17:40
  • I didn't get any option to delete item from dict but i tried to copy from one dict other with filter. Facing issue in filtering list item inside dict and appending it back. – Arjun Kanti Jan 25 '20 at 04:44
  • @ArjunKanti tip to get a better experience here: the best way to respond to the kind of question asked by Ed Ward is **not** to leave a comment. Edit you question, add the code you wrote, show how you run it, explain your issue(s), show the errors you get. – Zeitounator Jan 25 '20 at 14:19

1 Answers1

1

Simple solution

Let's create a list of dictionaries that shall be removed

  vars:
    ritem:
      - name: serviceName2
        manifest: someManifest2
        source: efgh.tgz
        files:
          - file3.tar
          - file4.tar

The tasks below do the job

    - set_fact:
        inst2: "{{ installers|selectattr('packages', 'defined')|list }}"
    - set_fact:
        inst1: "{{ installers|difference(inst2) }}"
    - set_fact:
        inst4: "{{ inst4|default(inst1) + [
                   item|combine({'packages': item.packages|difference(ritem)})] }}"
      loop: "{{ inst2 }}"
    - debug:
        var: inst4

give

    "inst4": [
        {
            "name": "someName1", 
            "version": 1.0
        }, 
        {
            "name": "someName2", 
            "version": 2.0
        }, 
        {
            "name": "someName3", 
            "packages": [
                {
                    "fileName": "fileName1", 
                    "version": "1.1.1"
                }, 
                {
                    "fileName": "fileName2", 
                    "version": "2.2.2"
                }
            ]
        }, 
        {
            "name": "service", 
            "packages": [
                {
                    "files": [
                        "file1.tar", 
                        "file2.tar"
                    ], 
                    "manifest": "someManifest2", 
                    "name": "serviceName1", 
                    "source": "abcd.tgz"
                }, 
                {
                    "files": [
                        "file5.tar", 
                        "file6.tar"
                    ], 
                    "manifest": "someManifest3", 
                    "name": "serviceName3", 
                    "source": "ijkl.tgz"
                }
            ], 
            "type": "install"
        }
    ]

Formatting of the dictionaries

The simple solution does not work if the dictionaries are not formatted (sorted) exactly the same way. For example, if the files' order differs, the item won't be removed

  vars:
    ritem:
      - name: serviceName2
        manifest: someManifest2
        source: efgh.tgz
        files:
          - file4.tar
          - file3.tar

This can be fixed with a couple of custom filters. Let's create the list rhash to simplify the comparison of the dictionaries and put it to vars

rhash: "{{ ritem|map('dict_flatten')|map('hash')|list }}"

The tasks below give the same result

    - set_fact:
        inst3: "{{ inst3|default([]) + [
                   filter|
                   list_select_list_bool(item.packages, negative=True)|
                   list] }}"
      vars:
        filter: "{{ item.packages|
                    map('dict_flatten')|
                    map('hash')|
                    map('bool_in', rhash)|
                    list }}"
      loop: "{{ inst2 }}"
    - set_fact:
      inst4: "{{ inst4|default(inst1) + [
                 item.0|combine({'packages': item.1})] }}"
      loop: "{{ inst2|zip(inst3)|list }}"

    - debug:
        var: inst4

Custom filters

$ cat filter_plugins/filers.py
def bool_in(x, l):
    return (x in l)

def dict_flatten(d, separator='.'):
    out = {}
    def flatten(x, name=''):
        if type(x) is dict:
            for a in x:
                flatten(x[a], name + a + separator)
        elif type(x) is list:
            i = 0
            for a in sorted(x):
                flatten(a, name + str(i) + separator)
                i += 1
        else:
            out[name[:-1]] = x
    flatten(d)
    return out

def list_select_list_bool(b, l, negative=False):
    l2=[]
    for bi,li in zip(b,l):
        if negative:
            if not bi:
                l2.append(li)
        else:
            if bi:
                l2.append(li)
    return l2

class FilterModule(object):

    def filters(self):
        return {
            'bool_in': bool_in,
            'dict_flatten': dict_flatten,
            'list_select_list_bool': list_select_list_bool
            }
Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63