Search code examples
pythonpython-3.xdictionarykey-value

How can I remove a key:value pair wherever the chosen key occurs in a deeply nested dictionary?


About

This question is about the most basic problem of deleting a key:value pair at a found key, iterating over a whole dictionary.

Other questions


Before:

I have a dictionary that is nested many times.

{
    "key0": {
        "key1a": {
            "sub_key2a": "sub_value2a",
            "sub_key2b": "sub_value2b"
        },
        "key1b": {
            "key_XYZ": {
                "sub_key2a": "sub_value2a",
                "sub_key2b": "sub_value2b"
            }
        }
    }
}

After:

The result should look like this, deleting all "sub_key2a" keys with their values:

{
    "key0": {
        "key1a": {
            "sub_key2b": "sub_value2b"
        },
        "key1b": {
            "key_XYZ": {
                "sub_key2b": "sub_value2b"
            }
        }
    }
}

Modifying a Python dict while iterating over it

When I looped through the items of the dictionary to delete, I got the error

RuntimeError: dictionary changed size during iteration

which needs somehow to be avoided.

How can I remove the "sub_key2a": SOME_VALUE key-value pair each time the key "sub_key2a" occurs somewhere in the dictionary?


Solution

  • Trick

    The trick is to find out in advance whether a target_key is among the next children (= this_dict[key] = the values of the current dict iteration) before you reach the child level recursively. Only then you can still delete a key:value pair of the child level while iterating over a dictionary. Once you have reached the same level as the key to be deleted and then try to delete it from there, you get the error:

    RuntimeError: dictionary changed size during iteration
    

    Code

    Thus, the code looks as follows:

    import copy
    
    def find_remove(this_dict, target_key, bln_overwrite_dict=False):
        if not bln_overwrite_dict:
            this_dict = copy.deepcopy(this_dict)
    
        for key in this_dict:
            # if the current value is a dict, dive into it
            if isinstance(this_dict[key], dict):
                if target_key in this_dict[key]:
                    this_dict[key].pop(target_key)
    
                this_dict[key] = find_remove(this_dict[key], target_key)
    
        return this_dict
    
    dict_nested_new = find_remove(nested_dict, "sub_key2a")
    

    Credits

    This is almost a copy of the spin-off How can I replace a key:value pair by its value wherever the chosen key occurs in a deeply nested dictionary?. But it took me a while to change that answer so that it would delete a key:value by its key. That is why I am sharing this, please mind that 95% of the credits go to the link!

    The main added value over the "spun-off answer" is that you search for the target_key in the values in advance of entering the next recursion level by checking if target_key in this_dict[key]:.


    Side note: Formatting the output

    If you want to print or save the dictionary nicely, see How do I write JSON data to a file?.