1

I have a dictionary which contains dictionaries, which may in turn contain dictionaries ad infinitum. I want to change every key in all of the dictionaries, with the exception of the keys which map to one of the nested dictionaries. I understand that the keys are immutable, what I want to do something like this:

layer[item + '_addition'] = layer.pop(item)

What I have right now is:

def alterKeys(item, layer=topLevelDict):
    if isinstance(item, dict):
        for i in item:
            alterKeys(item[i], item)
    else:
        layer[item + '_addition'] = layer.pop(item)

This doesn't work, as it will continually travel recursively down the tree till the last line tries to pop a value from the dict, instead of a key, which raises a KeyError. I know I'm close to a solution, but I've been thinking about this for a few minutes and I can't seem to figure it out.

Will Da Silva
  • 6,386
  • 2
  • 27
  • 52

3 Answers3

1

While writing this question, I figured it out. I figured I'd post this in case someone else has the same question at some point.

def alterKeys(item, layer=topLevelDict, key=None):
    if isinstance(item, dict):
        for k, v in item.items():
            alterKeys(v, item, k)
    elif isinstance(key, str):
        layer[key + '_addition'] = layer.pop(key)

If anyone has a more elegant or otherwise better solution, I'd love to see it. I just tested this script out by running a json file through it and it renamed every key that wasn't mapping to a dictionary, which is exactly what I wanted.

If I wanted every key to be renamed, including those which mapped to the nested dictionaries, I could use this code instead:

def alterKeys(item, layer=topLevelDict, key=None):
    if isinstance(item, dict):
        for k, v in item.items():
            alterKeys(v, item, k)
    if isinstance(key, str):
        layer[key + '_addition'] = layer.pop(key)

If you're using python2, remember to use .iteritems() instead of .items(), and use isinstance(key, basestring) instead of isinstance(key, str).

Will Da Silva
  • 6,386
  • 2
  • 27
  • 52
  • This in-place replacement throws `RuntimeError: dictionary keys changed during iteration` - This might be a better solution: [link](https://stackoverflow.com/a/11700817/3372071) – piritocle Aug 23 '21 at 13:33
1

I came across this question when searching for a solution to a slight variant of this so I thought I would post my own solution for python 3.7.4.

The following code will iterate through an object which is a nested combination of ordered dict and list and the following code will rename all fields named @index to id.

from collections import OrderedDict

def rename_index_to_id(iterable):
    if type(iterable) is OrderedDict:
        for key in list(iterable.keys()):
            if key == "@index":
                iterable["id"] = iterable["@index"]
                del iterable[key]
        for obj in iterable:
            rename_index_to_id(iterable[obj])
    elif type(iterable) is list:
        for obj in iterable:
            rename_index_to_id(obj)
Will Da Silva
  • 6,386
  • 2
  • 27
  • 52
Martin Riddar
  • 173
  • 4
  • 16
0

I modified the solution from @martin-riddar to make it more generic.

from collections import OrderedDict
def rename_dict_key(the_dict, old_key_name, new_key_name):
  if type(the_dict) in [dict, OrderedDict]:
    for key in list(the_dict.keys()):
      if key == old_key_name:
        the_dict[new_key_name] = the_dict[old_key_name]
        del the_dict[key]
    for obj in the_dict:
      rename_dict_key(the_dict[obj], old_key_name, new_key_name)
  elif type(the_dict) is list:
    for obj in the_dict:
      rename_dict_key(obj, old_key_name, new_key_name)

Note: I tested this with a dict, not an OrderedDict, although I don't expect the results would be any different.

B D T
  • 53
  • 6