Python: Replace values in nested dictionary - python

I want to replace the values (formated as strings) with the same values as integers, whenever the key is 'current_values'.
d = {'id': '10', 'datastreams': [{'current_value': '5'}, {'current_value': '4'}]}
Desired Output:
d = {'id': '10', 'datastreams': [{'current_value': 5}, {'current_value': 4}]}

The following piece of code replaces (substrings of) values in a dictionary. It works for nested json structures and copes with json, list and string types. You can easily add other types if needed.
def dict_replace_value(d: dict, old: str, new: str) -> dict:
x = {}
for k, v in d.items():
if isinstance(v, dict):
v = dict_replace_value(v, old, new)
elif isinstance(v, list):
v = list_replace_value(v, old, new)
elif isinstance(v, str):
v = v.replace(old, new)
x[k] = v
return x
def list_replace_value(l: list, old: str, new: str) -> list:
x = []
for e in l:
if isinstance(e, list):
e = list_replace_value(e, old, new)
elif isinstance(e, dict):
e = dict_replace_value(e, old, new)
elif isinstance(e, str):
e = e.replace(old, new)
x.append(e)
return x
# See input and output below
output = dict_replace_value(input, 'string', 'something')
Input:
input = {
'key1': 'a string',
'key2': 'another string',
'key3': [
'a string',
'another string',
[1, 2, 3],
{
'key1': 'a string',
'key2': 'another string'
}
],
'key4': {
'key1': 'a string',
'key2': 'another string',
'key3': [
'a string',
'another string',
500,
1000
]
},
'key5': {
'key1': [
{
'key1': 'a string'
}
]
}
}
Output:
print(output)
{
"key1":"a something",
"key2":"another something",
"key3":[
"a something",
"another something",
[
1,
2,
3
],
{
"key1":"a something",
"key2":"another something"
}
],
"key4":{
"key1":"a something",
"key2":"another something",
"key3":[
"a something",
"another something",
500,
1000
]
},
"key5":{
"key1":[
{
"key1":"a something"
}
]
}
}

d = {'id': '10', 'datastreams': [{'current_value': '5'}, {'current_value': '4'}]}
for elem in d['datastreams']: # for each elem in the list datastreams
for k,v in elem.items(): # for key,val in the elem of the list
if 'current_value' in k: # if current_value is in the key
elem[k] = int(v) # Cast it to int
print(d)
OUTPUT:
{'id': '10', 'datastreams': [{'current_value': 5}, {'current_value': 4}]}

A general approach (assuming you don't know in advance which key of the dict is pointing to a list) would be to iterate over the dict and check the type of its values and then iterate again into each value if needed.
In your case, your dictionary may contain a list of dictionaries as values, so it is enough to check if a value is of type list, if so, iterate over the list and change the dicts you need.
It can be done recursively with a function like the following:
def f(d):
for k,v in d.items():
if k == 'current_value':
d[k] = int(v)
elif type(v) is list:
for item in v:
if type(item) is dict:
f(item)
>>> d = {'id': '10', 'datastreams': [{'current_value': '5'}, {'current_value': '4'}]}
>>> f(d)
>>> d
{'id': '10', 'datastreams': [{'current_value': 5}, {'current_value': 4}]}

Can be done with list comprehension:
d['datastreams'] = [{'current_value': int(ds['current_value'])} if ('current_value' in ds) else ds for ds in d['datastreams']]

You can use ast.literal_eval to evaluate the underlying value for items with current_value key in the d['datastreams'] list. Then check whether the type is an int using isinstance for such values. Finally, type cast such values to int.
import ast
d = {'id': '10', 'datastreams': [{'current_value': '5'}, {'current_value': '4'}]}
for i in d['datastreams']:
for k,v in i.items():
if 'current_value' in k and isinstance(ast.literal_eval(v),int):
i[k] = int(v)
#Output:
print(d)
{'id': '10', 'datastreams': [{'current_value': 5}, {'current_value': 4}]}

You could use this method
which would loop through checks for current_value in list and change it to integer by passing the value through int() function:
for value in d.values():
for element in value:
if 'current_value' in element:
element['current_value'] = int(element['current_value'])

Taking alec_djinn's solution little farther to handle also nested dicts:
def f(d):
for k,v in d.items():
if k == 'current_value':
d[k] = int(v)
elif type(v) is list:
for item in v:
if type(item) is dict:
f(item)
if type(v) is dict:
f(v)

Related

Add dictionary if key value is empty using python

I have a dictionary with missing values (the key is there, but the associated value is empty). For example I want the dictionary below:
dct = {'ID': '', 'gender': 'male', 'age': '20', 'weight': '', 'height': '5.7'}
to be changed to this form:
dct = {'ID': {'link': '','value': ''}, 'gender': 'male', 'age': '20', 'weight': {'link': '','value': ''}, 'height': '5.7'}
I want the ID and Weight key should be replaced with nested dictionary if its empty.
How can I write that in the most time-efficient way?
I have tried solutions from below links but didnt work,
def update(orignal, addition):
for k, v in addition.items():
if k not in orignal:
orignal[k] = v
else:
if isinstance(v, dict):
update(orignal[k], v)
elif isinstance(v, list):
for i in range(len(v)):
update(orignal[k][i], v[i])
else:
if not orignal[k]:
orignal[k] = v
Error: TypeError: 'str' object does not support item assignment
Fill missing keys by comparing example json in python
Adding missing keys in dictionary in Python
It seems similar with this issue https://stackoverflow.com/a/3233356/6396981
import collections.abc
def update(d, u):
for k, v in u.items():
if isinstance(v, collections.abc.Mapping):
d[k] = update(d.get(k, {}) or {}, v)
else:
d[k] = v
return d
For example in your case:
>>> dict1 = {'ID':'', 'gender':'male', 'age':'20', 'weight':'', 'height':'5.7'}
>>> dict2 = {'ID': {'link':'','value':''}, 'weight': {'link':'','value':''}}
>>>
>>> update(dict1, dict2)
{'ID': {'link': '', 'value': ''}, 'gender': 'male', 'age': '20', 'weight': {'link': '', 'value': ''}, 'height': '5.7'}
>>>
You can iterate through the list and see if the value is an empty string('') if it is, replace it with the default value. Here's a small snippet which does it -
dct = {'ID':'', 'gender':'male', 'age':'20', 'weight':'', 'height':'5.7'}
def update(d, default):
for k, v in d.items():
if v == '':
d[k] = default.copy()
update(dct, {'link':'','value':''})
print(dct)
Output :
{'ID': {'link': '', 'value': ''}, 'gender': 'male', 'age': '20', 'weight': {'link': '', 'value': ''}, 'height': '5.7'}
Note that the dict is passed by reference to the function, so any updates made there will be reflected in the original dictionary as well as seen in the above example.
If your dict is nested and you want the replacement to be done for nested items as well then you can use this function -
def nested_update(d, default):
for k, v in d.items():
if v == '':
d[k] = default.copy()
if isinstance(v, list):
for item in v:
nested_update(item, default)
if isinstance(v, dict):
nested_update(v, default)
here's a small example with list of dictionaries and nested dictionary -
dct = {'ID':'', 'gender':'male', 'age':'20', 'weight':'', 'height':'5.7', "list_data":[{'empty': ''}, {'non-empty': 'value'}], "nested_dict": {"key1": "val1", "missing_nested": ""}}
nested_update(dct, {'key1': 'val1-added', 'key2': 'val2-added'})
print(dct)
Output :
{'ID': {'key1': 'val1-added', 'key2': 'val2-added'}, 'gender': 'male', 'age': '20', 'weight': {'key1': 'val1-added', 'key2': 'val2-added'}, 'height': '5.7', 'list_data': [{'empty': {'key1': 'val1-added', 'key2': 'val2-added'}}, {'non-empty': 'value'}], 'nested_dict': {'key1': 'val1', 'missing_nested': {'key1': 'val1-added', 'key2': 'val2-added'}}}
For "this default dictionary to only specified keys like ID and Weight and not for other keys", you can update the condition of when we replace the value -
def nested_update(d, default):
for k, v in d.items():
if k in ('ID', 'weight') and v == '':
d[k] = default.copy()
if isinstance(v, list):
for item in v:
nested_update(item, default)
if isinstance(v, dict):
nested_update(v, default)

Reemplacing a list into a dictionary by another dictionary

I have a dictionary with some values that are type list, i need to convert each list in another dictionary and insert this new dictionary at the place of the list.
Basically, I have this dictionary
Dic = {
'name': 'P1',
'srcintf': 'IntA',
'dstintf': 'IntB',
'srcaddr': 'IP1',
'dstaddr': ['IP2', 'IP3', 'IP4'],
'service': ['P_9100', 'SNMP'],
'schedule' : 'always',
}
I need to reemplace the values that are lists
Expected output:
Dic = {
'name': 'P1',
'srcintf': 'IntA',
'dstintf': 'IntB',
'srcaddr': 'IP1',
'dstaddr': [
{'name': 'IP2'},
{'name': 'IP3'},
{'name': 'IP4'}
],
'service': [
{'name': 'P_9100'},
{'name': 'SNMP'}
],
'schedule' : 'always',
}
So far I have come up with this code:
for k,v in Dic.items():
if not isinstance(v, list):
NewDic = [k,v]
print(NewDic)
else:
values = v
keys = ["name"]*len(values)
for item in range(len(values)):
key = keys[item]
value = values[item]
SmallDic = {key : value}
liste.append(SmallDic)
NewDic = [k,liste]
which print this
['name', 'P1']
['srcintf', 'IntA']
['dstintf', 'IntB']
['srcaddr', 'IP1']
['schedule', 'always']
['schedule', 'always']
I think is a problem with the loop for, but so far I haven't been able to figure it out.
You need to re-create the dictionary. With some modifications to your existing code so that it generates a new dictionary & fixing the else clause:
NewDic = {}
for k, v in Dic.items():
if not isinstance(v, list):
NewDic[k] = v
else:
NewDic[k] = [
{"name": e} for e in v # loop through the list values & generate a dict for each
]
print(NewDic)
Result:
{'name': 'P1', 'srcintf': 'IntA', 'dstintf': 'IntB', 'srcaddr': 'IP1', 'dstaddr': [{'name': 'IP2'}, {'name': 'IP3'}, {'name': 'IP4'}], 'service': [{'name': 'P_9100'}, {'name': 'SNMP'}], 'schedule': 'always'}

How can I remove parts of a string and None values from an item in from list of dictionaries in python?

I am trying to remove all the '/api/1/employees/' and /api/1/seats/ from the python list of dictionaries. What is the easiest way to do so. My list of dictionaries are looking like this at the moment -dict1 = [{'to_seat_url': '/api/1/seats/19014', 'id': 5051, 'employee_url': '/api/1/employees/4027'}, {'to_seat_url': '/api/1/seats/19013', 'id': 5052, 'employee_url': '/api/1/employees/4048'}, {'to_seat_url': '/api/1/seats/19012', 'id': 5053, 'employee_url': '/api/1/employees/4117'}, {'to_seat_url': None, 'id': 5054, 'employee_url': '/api/1/employees/15027'}]
excepting below result:
new_dict = [{'to_seat_url': '19014', 'id': 5051, 'employee_url': '4027'}, {'to_seat_url': '19013', 'id': 5052, 'employee_url': '4048'}, {'to_seat_url': '19012', 'id': 5053, 'employee_url': '4117'}, {'to_seat_url': '', 'id': 5054, 'employee_url': '15027'}]
If you want last part of the URLs:
new_dict = [
{k: v.split("/")[-1] if k != "id" else v for k, v in d.items()}
for d in dict1
]
print(new_dict)
Prints:
[
{"to_seat_url": "19014", "id": 5051, "employee_url": "4027"},
{"to_seat_url": "19013", "id": 5052, "employee_url": "4048"},
{"to_seat_url": "19012", "id": 5053, "employee_url": "4117"},
{"to_seat_url": "9765", "id": 5054, "employee_url": "15027"},
]
EDIT: To filter-out None values:
new_dict = [
{k: v.split("/")[-1] if k != "id" else v for k, v in d.items()}
for d in dict1
if not d is None
]
print(new_dict)
EDIT 2: If some values are None:
new_dict = [
{
k: ("" if v is None else v.split("/")[-1]) if k != "id" else v
for k, v in d.items()
}
for d in dict1
if not d is None
]
print(new_dict)

Replace keys in a nested dictionary

I have a nested dictionary {1: {2: {3: None}}} and a dictionary that maps keys of the nested dictionary to a set of values such as {1: x, 2: y, 3: z}. I want to transform the nested dictionary to this form {x: {y: {z: None}}}. I have tried a couple of recursive functions but I keep going in circles and confusing myself. What is the best way to achieve this?
The level of nesting is arbitrary. The above is a simple example.
You need to recurse through the dictionary while building a new one with new keys. Note that if you have a list or tuple in there somewhere that has other dictionaries in it, they won't be processed - you'd have to add some code to do that. You can actually do this without building a new dictionary, but I think this way is simpler.
od = { 1: { 2: { 3: None }}}
kd = { 1: 'x', 2: 'y', 3: 'z' }
def replace_keys(old_dict, key_dict):
new_dict = { }
for key in old_dict.keys():
new_key = key_dict.get(key, key)
if isinstance(old_dict[key], dict):
new_dict[new_key] = replace_keys(old_dict[key], key_dict)
else:
new_dict[new_key] = old_dict[key]
return new_dict
nd = replace_keys(od, kd)
print nd
outputs:
{'x': {'y': {'z': None}}}
The accepted answer will not support dict of list, adding the full feature
#bilentor,
od = {'name': 'John', '1': [{'name': 'innername'}]}
kd = { 'name': 'cname', '1': '2', 3: 'z' }
def replace_keys(data_dict, key_dict):
new_dict = { }
if isinstance(data_dict, list):
dict_value_list = list()
for inner_dict in data_dict:
dict_value_list.append(replace_keys(inner_dict, key_dict))
return dict_value_list
else:
for key in data_dict.keys():
value = data_dict[key]
new_key = key_dict.get(key, key)
if isinstance(value, dict) or isinstance(value, list):
new_dict[new_key] = replace_keys(value, key_dict)
else:
new_dict[new_key] = value
return new_dict
nd = replace_keys(od, kd)
print(nd)
You can use a NestedDict
from ndicts import NestedDict
d = {1: {2: {3: None}}}
replace = {1: 'x', 2: 'y', 3: 'z'}
def ndict_replace(ndict: dict, map: dict):
nd = NestedDict(nd)
new_nd = NestedDict()
for key, value in nd.items():
new_key = tuple(replace.get(k, k) for k in key)
new_nd[new_key] = value
return new_nd.to_dict()
>>> ndict_replace(d, replace)
{'x': {'y': {'z': None}}}
The solution is robust and works with any nested dictionary
>>> d = {
1: {2: {3: None}},
3: {4: None},
5: None
}
>>> ndict_replace(d, replace)
{'x': {'y': {'z': None}}, 'z': {4: None}, 4: None}}
To install ndicts pip install ndicts

python: a way to get an exhaustive, sorted list of keys in a nested dictionary?

exhaustive:
- all keys in the dictionary, even if the keys are in a nested dictionary that is a value to a previous-level dictionary key.
sorted:
- this is to ensure the keys are always returned in the same order
The nesting is arbitrarily deep. A non-recursive algorithm is preferred.
level1 = {
'a' : 'aaaa',
'level2_1' : {'b': 'bbbbb', 'level3': {'c': 'cccc', 'd': 'dddddd'} },
'level2_2' : { 'z': 'zzzzzzz' }
}
Note: dictionary values can include lists (which can have dictionaries as elements), e.g.
tricky = {'category': [{'content': 'aaaaa'}, {'content': 'bbbbbb'}]}
def _auxallkeys(aset, adict):
aset.update(adict)
for d in adict.itervalues():
if isinstance(d, dict):
_auxallkeys(aset, d)
def allkeys(adict):
aset = set()
_auxallkeys(aset, adict)
return sorted(aset)
is the obvious (recursive) solution. To eliminate recursion:
def allkeys(adict):
aset = set()
pending = [adict]
while pending:
d = pending.pop()
aset.update(d)
for dd in d.itervalues():
if isinstance(dd, dict):
pending.append(dd)
return sorted(aset)
since the order of processing of the various nested dicts does not matter for this purpose.
Edit: the OP comments whining that it doesn't work if a dict is not nested, but rather in a list (and I replied that it could also be in a tuple, an object with attributes per-instance or per-class [maybe a base class thereof], a shelf, and many other ways to hide dicts around the house;-). If the OP will deign to define precisely what he means by "nested" (obviously not the same meaning as ordinary mortals apply to the word in question), it will probably be easier to help him. Meanwhile, here's a version that covers lists (and tuples, but not generators, instances of many itertools classes, shelves, etc, etc);
def allkeys(adict):
aset = set()
pending = [adict]
pendlis = []
def do_seq(seq):
for dd in seq:
if isinstance(dd, dict):
pending.append(dd)
elif isinstance(dd, (list, tuple)):
pendlis.append(dd)
while pending or pendlis:
while pending:
d = pending.pop()
aset.update(d)
do_seq(d.itervalues())
while pendlis:
l = pendlis.pop()
do_seq(l)
return sorted(aset)
A non-recursive method isn't obvious to me right now. The following works on your original example. Edit: It will now handle dicts within a list within a dict, at least the one within the tricky example cited in the comment to Alex Martelli's answer.
#!/usr/bin/env python
import types
def get_key_list(the_dict, key_list):
for k, v in (the_dict.iteritems()):
key_list.append(k)
if type(v) is types.DictType:
get_key_list(v, key_list)
if type(v) is types.ListType:
for lv in v:
if type(lv) is types.DictType:
get_key_list(lv, key_list)
return
level1 = {
'a' : 'aaaa',
'level2_1' : {'b': 'bbbbb', 'level3': {'c': 'cccc', 'd': 'dddddd'} },
'level2_2' : { 'z': 'zzzzzzz' }
}
tricky = {'category': [{'content': 'aaaaa'}, {'content': 'bbbbbb'}]}
key_list = []
get_key_list(level1, key_list)
key_list.sort()
print key_list
key_list = []
get_key_list(tricky, key_list)
key_list.sort()
print key_list
Output:
['a', 'b', 'c', 'd', 'level2_1', 'level2_2', 'level3', 'z']
['category', 'content', 'content']
Here's a non-recursive solution which processes generators as well as lists, tuples and dicts and adds all successive keys if a key appears more than once:
def get_iterator(i):
if hasattr(i, 'next'):
# already an iterator - use it as-is!
return i
elif hasattr(i, '__iter__') and not isinstance(i, basestring):
# an iterable type that isn't a string
return iter(i)
else:
# Can't iterate most other types!
return None
def get_dict_keys(D):
LRtn = []
L = [(D, get_iterator(D))]
while 1:
if not L: break
cur, _iter = L[-1]
if _iter:
# Get the next item
try:
i = _iter.next()
except StopIteration:
del L[-1]
continue
if isinstance(cur, dict):
# Process a dict and all subitems
LRtn.append(i)
_iter = get_iterator(cur[i])
if _iter: L.append((cur[i], _iter))
else:
# Process generators, lists, tuples and all subitems
_iter = get_iterator(i)
if _iter: L.append((i, _iter))
# Sort and return
LRtn.sort()
return LRtn
D = {
'a' : 'aaaa',
'level2_1' : {'b': 'bbbbb', 'level3': {'c': 'cccc', 'd': 'dddddd', 'e': 134, 'f': [{'blah': 553}]} },
'level2_2' : { 'z': 'zzzzzzz' },
'blah2': iter([{'blah3': None}]),
}
print get_dict_keys(D)
EDIT: Increased the speed a bit and made the code shorter.
I also prefer a recursive approach...
#!/usr/bin/env python
def extract_all_keys(structure):
try:
list_of_keys = structure.keys()
for value in structure.values():
add_all_keys_in_value_to_list(value, list_of_keys)
except AttributeError:
list_of_keys = []
return list_of_keys.sort()
def add_all_keys_in_value_to_list(value, list_of_keys):
if isinstance(value, dict):
list_of_keys += extract_all_keys(value)
elif isinstance(value, (list, tuple)):
for element in value:
list_of_keys += extract_all_keys(element)
import unittest
class TestKeys(unittest.TestCase):
def given_a_structure_of(self, structure):
self.structure = structure
def when_keys_are_extracted(self):
self.list_of_keys = extract_all_keys(self.structure)
def testEmptyDict(self):
self.given_a_structure_of({})
self.when_keys_are_extracted()
self.assertEquals(self.list_of_keys, [])
def testOneElement(self):
self.given_a_structure_of({'a': 'aaaa'})
self.when_keys_are_extracted()
self.assertEquals(self.list_of_keys, ['a'])
def testTwoElementsSorted(self):
self.given_a_structure_of({
'z': 'zzzz',
'a': 'aaaa',
})
self.when_keys_are_extracted()
self.assertEquals(self.list_of_keys, ['a', 'z'])
def testNestedElements(self):
self.given_a_structure_of({
'a': {'aaaa': 'A',},
'b': {'bbbb': 'B',},
})
self.when_keys_are_extracted()
self.assertEquals(self.list_of_keys, ['a', 'aaaa', 'b', 'bbbb'])
def testDoublyNestedElements(self):
self.given_a_structure_of({
'level2': {'aaaa': 'A',
'level3': {'bbbb': 'B'}
}
})
self.when_keys_are_extracted()
self.assertEquals(self.list_of_keys, ['aaaa', 'bbbb', 'level2', 'level3'])
def testNestedExampleOnStackOverflow(self):
level1 = {
'a' : 'aaaa',
'level2_1' : {'b': 'bbbbb', 'level3': {'c': 'cccc', 'd': 'dddddd'} },
'level2_2' : { 'z': 'zzzzzzz' }
}
self.given_a_structure_of(level1)
self.when_keys_are_extracted()
self.assertEquals(self.list_of_keys, ['a', 'b', 'c', 'd', 'level2_1', 'level2_2', 'level3', 'z'])
def testListExampleOnStackOverflow(self):
tricky = {'category': [{'content': 'aaaaa'}, {'content': 'bbbbbb'}]}
self.given_a_structure_of(tricky)
self.when_keys_are_extracted()
self.assertEquals(self.list_of_keys, ['category', 'content', 'content'])
def testTuplesTreatedLikeLists(self):
tricky_tuple = {'category': ({'content': 'aaaaa'}, {'content': 'bbbbbb'})}
self.given_a_structure_of(tricky_tuple)
self.when_keys_are_extracted()
self.assertEquals(self.list_of_keys, ['category', 'content', 'content'])
def testCanHandleString(self):
self.given_a_structure_of('keys')
self.when_keys_are_extracted()
self.assertEquals(self.list_of_keys, [])
if __name__ == '__main__':
unittest.main()
I think it is better to use a recursive method.
My code is in the following.
level1 = {
'a' : 'aaaa',
'level2_1' : {'b': 'bbbbb', 'level3': {'c': 'cccc', 'd': 'dddddd'} },
'level2_2' : { 'z': 'zzzzzzz' }
}
all_keys=[] # a global list to store all the keys in level1
def depth ( dict ):
for k in dict:
if type(dict[k]) == type(dict): #judge the type of elements in dictionary
depth(dict[k]) # recursive
else:
all_keys.append(k)
depth(level1)
print all_keys

Categories