91 lines
3.0 KiB
Python
91 lines
3.0 KiB
Python
from typing import Any, Dict, List
|
|
|
|
|
|
def deep_merge(a: Dict[str, Any], b: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Recursively merge b into a and return a new dict.
|
|
|
|
Lists are concatenated; scalar values in b override a.
|
|
"""
|
|
result = dict(a)
|
|
for k, v in b.items():
|
|
# Key from b also in a
|
|
if k in result:
|
|
# a[k] and b[k] are both a dictionary: recursive deep_merge
|
|
if isinstance(result[k], dict) and isinstance(v, dict):
|
|
result[k] = deep_merge(result[k], v)
|
|
# a[k] and b[k] are both a list: list_merge
|
|
elif isinstance(result[k], list) and isinstance(v, list):
|
|
result[k] = list_merge(result[k], v)
|
|
else:
|
|
result[k] = v
|
|
else:
|
|
result[k] = v
|
|
return result
|
|
|
|
def list_merge(a: List[Any], b: List[Any]) -> List[Any]:
|
|
"""
|
|
Merge two list together based on id's. This is similar to !extend in YAML.
|
|
|
|
Steps:
|
|
1. Check if both lists contain Dicts with an `id` key in them
|
|
1a. Otherwise just return a + b (extend)
|
|
2. For every entry in a:
|
|
3. Store id in a list for later
|
|
4. Find the same id in b, if not found add to result without merge
|
|
5. Perform a deep_merge on the dictionary, add to result list
|
|
6. Ensure there are no id's missing from b, by checking every entry agains the list from step 3
|
|
7. Return result
|
|
"""
|
|
|
|
# If either list is empty just concatenate
|
|
if not a or not b:
|
|
return list(a) + list(b)
|
|
|
|
# Detect whether both lists contain dicts with an 'id' key
|
|
a_has_id = any(isinstance(x, dict) and "id" in x for x in a)
|
|
b_has_id = any(isinstance(x, dict) and "id" in x for x in b)
|
|
|
|
# If there are no id-based dicts in either list, just extend
|
|
if not (a_has_id and b_has_id):
|
|
return list(a) + list(b)
|
|
|
|
result: List[Any] = []
|
|
seen_ids = set()
|
|
|
|
# Merge entries from `a`, matching by `id` in `b` when present
|
|
for item in a:
|
|
if isinstance(item, dict) and "id" in item:
|
|
id_val = item.get("id")
|
|
# find matching entry in b
|
|
match = None
|
|
for bi in b:
|
|
if isinstance(bi, dict) and bi.get("id") == id_val:
|
|
match = bi
|
|
break
|
|
|
|
if match is not None:
|
|
# deep_merge returns a new dict
|
|
merged = deep_merge(item, match)
|
|
result.append(merged)
|
|
else:
|
|
result.append(item)
|
|
|
|
if id_val is not None:
|
|
seen_ids.add(id_val)
|
|
else:
|
|
# Not an id'd dict, keep as-is
|
|
result.append(item)
|
|
|
|
# Add remaining entries from b that were not merged or that don't have ids
|
|
for item in b:
|
|
if isinstance(item, dict) and "id" in item:
|
|
id_val = item.get("id")
|
|
if id_val is None:
|
|
result.append(item)
|
|
elif id_val not in seen_ids:
|
|
result.append(item)
|
|
else:
|
|
result.append(item)
|
|
|
|
return result
|