4

I'm trying to remove non-values from a nested dictionary. My first effort works fine, but unfortunately keys pointing to now empty dicts still persist.

So if i do:

pass1 = stripper(my_dict)
return stripper(pass1)

This works, but i'm thinking a more elegant nested solution might be possible?

def stripper(self, data):
    if isinstance(data, dict):
        d = ({k: stripper(v) for k, v in data.items()
             if v not in [u'', None]})
        if d:
            return d
    else:
        return data

Edit:

Failing example, dict below returns as {'foo': 'bar', 'bar': None}:

{
    'foo': 'bar',
    'bar': {
        'foo': None,
        'one': None
    }
}
rix
  • 9,209
  • 10
  • 60
  • 88
  • 6
    you usually dont pass my_dict to strippers... that doesnt end well – Joran Beasley Nov 04 '15 at 18:28
  • Can you post a very simple example where this doesn't work? If it leaves empty dicts on the first pass, then it seems likely that subsequent passes could leave keys with empty values as well... – mgilson Nov 04 '15 at 18:31

3 Answers3

8

The dict comprehension is certainly concise but if you expand it out, the solution becomes more obvious:

def stripper(self, data):
    new_data = {}
    for k, v in data.items():
        if isinstance(v, dict):
            v = stripper(v)
        if not v in (u'', None, {}):
            new_data[k] = v
    return new_data
Oliver Dain
  • 8,957
  • 3
  • 31
  • 45
  • Was missing the `{}` in the `not v in ('', None, {})` line - I hadn't tested it. Added that and it appears to me to work. Whomever down-voted, take another look as I believe this does the trick. – Oliver Dain Nov 04 '15 at 23:44
2

To extend the accepted answer to include objects that might be an element of a list:

def strip_empties_from_list(data):
    new_data = []
    for v in data:
        if isinstance(v, dict):
            v = strip_empties_from_dict(v)
        elif isinstance(v, list):
            v = strip_empties_from_list(v)
        if v not in (None, str(), list(), dict(),):
            new_data.append(v)
    return new_data


def strip_empties_from_dict(data):
    new_data = {}
    for k, v in data.items():
        if isinstance(v, dict):
            v = strip_empties_from_dict(v)
        elif isinstance(v, list):
            v = strip_empties_from_list(v)
        if v not in (None, str(), list(), dict(),):
            new_data[k] = v
    return new_data

To use:

data = {
  'None': None,
  'empty_list': [],
  'empty_dict': {},
  'empty_string': '',
  'list_with_empties': ['', {}, [], None, {'more_empties': '', 'and_even_more': [''], 'one_thing_i_care_about': 'hi'}]
}
stripped_data = strip_empties_from_dict(data)
print(stripped_data)
dnk8n
  • 595
  • 5
  • 21
0

Answer of @Oliver is correct, but having some edge cases checks won't hurt, here you go: (Was unable to edit Oliver's answer as the queue was full)

    def dictionary_stripper(data):
        new_data = {}
        
        # Only iterate if the given dict is not None
        if data:
            for k, v in data.items():
                if isinstance(v, dict):
                    v = CampaignAdminUtils.dictionary_stripper(v)
                
                # ideally it should be not in, second you can also add a empty list if required
                if v not in ("", None, {}, []):
                    new_data[k] = v
            
            # Only if you want the root dict to be None if empty
            if new_data == {}:
                return None
            return new_data
        return None
Sumit Badsara
  • 650
  • 9
  • 21