I have two dicts that I want to merge:
a = {"name": "john",
"phone":"123123123",
"owns": {"cars": "Car 1", "motorbikes": "Motorbike 1"}}
b = {"name": "john",
"phone":"123",
"owns": {"cars": "Car 2"}}
If a
and b
have a common key on the same nesting level, the result should be a list, with both values in it, which is assigned as the value for the shared key.
The result should look like this:
{"name": "john",
"phone":["123123123","123"],
"owns": {"cars": ["Car 1", "Car 2"], "motorbikes": "Motorbike 1"}}
Using a.update(b)
does not work since it overwrites the shared value of a
with the shared value of b
, such that result is something like this:
{'name': 'john', 'phone': '123', 'owns': {'cars': 'Car 2'}}
The goal is to merge the dicts without overwriting and to keep all information related to a specific key (in either of the dicts).
With recursion, you can build a dictionary comprehension that accomplishes that.
This solution also takes into account that you might want to later merge more than two dictionaries, flattening the list of values in that case.
def update_merge(d1, d2):
if isinstance(d1, dict) and isinstance(d2, dict):
# Unwrap d1 and d2 in new dictionary to keep non-shared keys with **d1, **d2
# Next unwrap a dict that treats shared keys
# If two keys have an equal value, we take that value as new value
# If the values are not equal, we recursively merge them
return {
**d1, **d2,
**{k: d1[k] if d1[k] == d2[k] else update_merge(d1[k], d2[k])
for k in {*d1} & {*d2}}
}
else:
# This case happens when values are merged
# It bundle values in a list, making sure
# to flatten them if they are already lists
return [
*(d1 if isinstance(d1, list) else [d1]),
*(d2 if isinstance(d2, list) else [d2])
]
Example:
a = {"name": "john", "phone":"123123123",
"owns": {"cars": "Car 1", "motorbikes": "Motorbike 1"}}
b = {"name": "john", "phone":"123", "owns": {"cars": "Car 2"}}
update_merge(a, b)
# {'name': 'john',
# 'phone': ['123123123', '123'],
# 'owns': {'cars': ['Car 1', 'Car 2'], 'motorbikes': 'Motorbike 1'}}
Example with more than two objects merged:
a = {"name": "john"}
b = {"name": "jack"}
c = {"name": "joe"}
d = update_merge(a, b)
d = update_merge(d, c)
d # {'name': ['john', 'jack', 'joe']}