Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python recursively replace character in keys of nested dictionary?

I'm trying to create a generic function that replaces dots in keys of a nested dictionary. I have a non-generic function that goes 3 levels deep, but there must be a way to do this generic. Any help is appreciated! My code so far:

output = {'key1': {'key2': 'value2', 'key3': {'key4 with a .': 'value4', 'key5 with a .': 'value5'}}}   def print_dict(d):     new = {}     for key,value in d.items():         new[key.replace(".", "-")] = {}         if isinstance(value, dict):             for key2, value2 in value.items():                 new[key][key2] = {}                 if isinstance(value2, dict):                     for key3, value3 in value2.items():                         new[key][key2][key3.replace(".", "-")] = value3                 else:                     new[key][key2.replace(".", "-")] = value2         else:             new[key] = value     return new  print print_dict(output) 

UPDATE: to answer my own question, I made a solution using json object_hooks:

import json  def remove_dots(obj):     for key in obj.keys():         new_key = key.replace(".","-")         if new_key != key:             obj[new_key] = obj[key]             del obj[key]     return obj  output = {'key1': {'key2': 'value2', 'key3': {'key4 with a .': 'value4', 'key5 with a .': 'value5'}}} new_json = json.loads(json.dumps(output), object_hook=remove_dots)   print new_json 
like image 409
Bas Tichelaar Avatar asked Jul 28 '12 11:07

Bas Tichelaar


2 Answers

Yes, there exists better way:

def print_dict(d):     new = {}     for k, v in d.iteritems():         if isinstance(v, dict):             v = print_dict(v)         new[k.replace('.', '-')] = v     return new 

(Edit: It's recursion, more on Wikipedia.)

like image 116
horejsek Avatar answered Sep 26 '22 01:09

horejsek


Actually all of the answers contain a mistake that may lead to wrong typing in the result.

I'd take the answer of @ngenain and improve it a bit below.

My solution will take care about the types derived from dict (OrderedDict, defaultdict, etc) and also about not only list, but set and tuple types.

I also do a simple type check in the beginning of the function for the most common types to reduce the comparisons count (may give a bit of speed in the large amounts of the data).

Works for Python 3. Replace obj.items() with obj.iteritems() for Py2.

def change_keys(obj, convert):     """     Recursively goes through the dictionary obj and replaces keys with the convert function.     """     if isinstance(obj, (str, int, float)):         return obj     if isinstance(obj, dict):         new = obj.__class__()         for k, v in obj.items():             new[convert(k)] = change_keys(v, convert)     elif isinstance(obj, (list, set, tuple)):         new = obj.__class__(change_keys(v, convert) for v in obj)     else:         return obj     return new 

If I understand the needs right, most of users want to convert the keys to use them with mongoDB that does not allow dots in key names.

like image 29
baldr Avatar answered Sep 26 '22 01:09

baldr