Credit to: @Gustavo Alves Casqueiro for original answer
I honestly would have preferred using a lib that could do the heavy lifting for me, but I just couldn't find something that did what I needed.
I have only added a couple of additional checks to this function.
I have included a check for lists
within a dict
and added a parameter for the name of a nested dict
to correctly update the nested dict
KEY when there may be another KEY within the OUTER dict
with the same name.
Updated function:
def update(dictionary: dict[str, any], key: str, value: any, nested_dict_name: str = None) -> dict[str, any]:
if not nested_dict_name: # if current (outermost) dict should be updated
if key in dictionary.keys(): # check if key exists in current dict
dictionary[key] = value
return dictionary
else: # if nested dict should be updated
if nested_dict_name in dictionary.keys(): # check if dict is in next layer
if isinstance(dictionary[nested_dict_name], dict):
if key in dictionary[nested_dict_name].keys(): # check if key exists in current dict
dictionary[nested_dict_name][key] = value
return dictionary
if isinstance(dictionary[nested_dict_name], list):
list_index = random.choice(range(len(dictionary[nested_dict_name]))) # pick a random dict from the list
if key in dictionary[nested_dict_name][list_index].keys(): # check if key exists in current dict
dictionary[nested_dict_name][list_index][key] = value
return dictionary
dic_aux = []
# this would only run IF the above if-statement was not able to identity and update a dict
for val_aux in dictionary.values():
if isinstance(val_aux, dict):
dic_aux.append(val_aux)
# call the update function again for recursion
for i in dic_aux:
return update(dictionary=i, key=key, value=value, nested_dict_name=nested_dict_name)
Original dict:
{
"level1": {
"level2": {
"myBool": "Original",
"myInt": "Original"
},
"myInt": "Original",
"myBool": "Original"
},
"myStr": "Original",
"level3": [
{
"myList": "Original",
"myInt": "Original",
"myBool": "Original"
}
],
"level4": [
{
"myList": "Original",
"myInt": "UPDATED",
"myBool": "Original"
}
],
"level5": {
"level6": {
"myBool": "Original",
"myInt": "Original"
},
"myInt": "Original",
"myBool": "Original"
}
}
Data for updating (using pytest
):
@pytest.fixture(params=[(None, 'myStr', 'UPDATED'),
('level1', 'myInt', 'UPDATED'),
('level2', 'myBool', 'UPDATED'),
('level3', 'myList', 'UPDATED'),
('level4', 'myInt', 'UPDATED'),
('level5', 'myBool', 'UPDATED')])
def sample_data(request):
return request.param
The 'UPDATED'
parameter doesn't make sense in this smaller use case (since I could just hard-code it), but for simplicity when reading the logs, I didn't want to see multiple data-types and just made it show me an 'UPDATED'
string.
Test:
@pytest.mark.usefixtures('sample_data')
def test_this(sample_data):
nested_dict, param, update_value = sample_data
if nested_dict is None:
print(f'\nDict Value: Level0\nParam: {param}\nUpdate Value: {update_value}')
else:
print(f'\nDict Value: {nested_dict}\nParam: {param}\nUpdate Value: {update_value}')
# initialise data dict
data_object = # insert data here (see example dict above)
# first print as is
print(f'\nOriginal Dict:\n{data_object}')
update(dictionary=data_object,
key=param,
value=update_value,
nested_dict_name=nested_dict)
# print updated
print(f'\nUpdated Dict:\n{data_object}')
There is one caveat, when you have a dict like this:
{
"level1": {
"level2": {
"myBool": "Original"
},
"myBool": "Original"
},
"level3": {
"level2": {
"myBool": "Original"
},
"myInt": "Original"
}
}
Where level2
is under level1
AND level3
. This would require making using of a list
or something with the nested_dict_name
and passing in the name of the outer dict
AND inner dict
(['level5', 'level2']
) and then somehow looping through the values to find that dict
.
However, since I haven't yet ran into this issue for the data objects I use, I haven't spent the time trying to solve this "issue".