Recursive dict flattener with dynamic key names Python

Viewed 78

So I am trying to recursively flatten a python dictionary but I am having issues when the list of one of the values has a len greater than 1. The structure of the data is always, but in value can 1 or greater...

{'id': 123,
 'in': [{'abs': 0.42, 'tpr': 0.99}, {'abs': 0.77, 'tpr': 0.98}],
 'out': {'vbv': None, 'xyz': None},
 'start': '20/06/2019'}

Here is the flatten I have already:

def flatten(d):
    out = {}
    for key, val in d.items():
        if isinstance(val, dict):
            val = [val]
        if isinstance(val, list):
            for subdict in val:
                deeper = flatten(subdict).items()
                out.update({key + '_' + key2: val2 for key2, val2 in deeper})
        else:
            out[key] = val
    return out

If I run this through the flatten func I end up with:

{'id': 123,
 'in_abs': 0.77,
 'in_tpr': 0.98,
 'out_vbv': None,
 'out_xyz': None,
 'start': '20/06/2019'}

So essentially I lose that first entry, I figured it might be an index issue so added a count to the out.update line, but its not that.

The desired output would be:

{'id': 123,
 'in_0_abs': 0.42,
 'in_0_tpr': 0.99,
 'in_1_abs': 0.77,
 'in_1_tpr': 0.98,
 'out_vbv': None,
 'out_xyz': None,
 'start': '20/06/2019'}

Working example:

import pprint as pp


def flatten(d):
    out = {}
    for key, val in d.items():
        if isinstance(val, dict):
            val = [val]
        if isinstance(val, list):
            for subdict in val:
                deeper = flatten(subdict).items()
                out.update({key + '_' + key2: val2 for key2, val2 in deeper})

        else:
            out[key] = val
    return out


if __name__ == '__main__':
    INPUT = {'id': 123, 'in': [{'abs': 0.42, 'tpr': 0.99}, {'abs': 0.77, 'tpr': 0.98}],
             'out': {'vbv': None, 'xyz': None},
             'start': '20/06/2019'}

    pp.pprint(flatten(INPUT))

EDIT

def flatten(d):
    out = {}
    for key, val in d.items():
        if isinstance(val, dict):
            val = [val]
        if isinstance(val, list):
            for index, subdict in enumerate(val):
                deeper = flatten(subdict).items()
                out.update({key + '_' + key2 + '_' + str(index): val2 for key2, val2 in deeper})

        else:
            out[key] = val
    return out

This will output:

{'id': 123,
 'in_abs_0': 0.42,
 'in_abs_1': 0.77,
 'in_tpr_0': 0.99,
 'in_tpr_1': 0.98,
 'out_vbv_0': None,
 'out_xyz_0': None,
 'start': '20/06/2019'}
2 Answers

You would need an index/count variable when you encounter a list.

def flatten(d):
    out = {}
    # pdb.set_trace()
    for key, val in d.items():
        if isinstance(val, dict):
            val = [val]
        if isinstance(val, list):
            count = 0
            for subdict in val:
                deeper = flatten(subdict).items()
                if (len(val) > 1):
                    out.update({key + '_' + str(count) + '_' + key2: val2 for key2, val2 in deeper})
                    count = count + 1
                else:
                    out.update({key + '_' + key2: val2 for key2, val2 in deeper})
        else:
            out[key] = val
    return out

OUTPUT

{'id': 123,
 'in_0_abs': 0.42,
 'in_0_tpr': 0.99,
 'in_1_abs': 0.77,
 'in_1_tpr': 0.98,
 'out_vbv': None,
 'out_xyz': None,
 'start': '20/06/2019'}

Using enumerate:

def flatten(d):
    out = {}
    for key, val in d.items():
        if isinstance(val, dict):
            val = [val]
        if isinstance(val, list):
            for index, subdict in enumerate(val):
                deeper = flatten(subdict).items()
                out.update({key + '_' + key2 + '_' + str(index): val2 for key2, val2 in deeper})

        else:
            out[key] = val
    return out
Related