## @file
# Copyright (c) 2023, The OCE Build Authors. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
##
"""Dictionary helper functions."""
from typing import Dict, List, Union, Optional
[docs]def flatten_dict(dic: dict,
delimiter: str='.'
) -> Dict[str, any]:
"""Flattens a dictionary.
Args:
dic: The dictionary to flatten.
delimiter (optional): custom key delimiter.
Returns:
A flattened dictionary
"""
flat_dict: dict={}
def recurse_flatten(v: any, prefix: str='') -> None:
if isinstance(v, dict):
if not (entries := v.items()):
flat_dict[prefix[1:]] = v
else:
for k, v2 in entries:
p2 = f"{prefix}{delimiter}{k}"
recurse_flatten(v2, p2)
elif isinstance(v, list):
if not v:
flat_dict[prefix[1:]] = v
else:
for i, v2 in enumerate(v):
p2 = f"{prefix}[{i}]"
recurse_flatten(v2, p2)
else:
flat_dict[prefix[1:]] = v
recurse_flatten(dic)
return flat_dict
[docs]def nested_get(dic: dict,
keys: List[str],
default: Optional[any]=None
) -> Union[dict, any, None]:
"""Retrieves a nested value from a dictionary.
Args:
dic: The dictionary to retrieve the value from.
keys: The keys to traverse the dictionary.
Returns:
The value at the end of the keys list.
"""
try:
for key in keys:
dic = dic[key]
return dic
except KeyError:
return default
[docs]def nested_set(dic: dict,
keys: List[str],
value: any
) -> None:
"""Sets a nested value in a dictionary.
Args:
dic: The dictionary to set the value in.
keys: The keys to traverse the dictionary.
value: The value to set.
"""
for key in keys[:-1]:
if isinstance(dic, dict):
dic = dic.setdefault(key, {})
else: return
dic[keys[-1]] = value
[docs]def nested_del(dic: dict,
keys: List[str]
) -> None:
"""Deletes a nested value in a dictionary.
Args:
dic: The dictionary to delete the value from.
keys: The keys to traverse the dictionary.
"""
for key in keys[:-1]: dic = dic[key]
del dic[keys[-1]]
[docs]def merge_dict(a: dict, b: dict) -> dict:
"""Merges two dictionaries recursively.
Args:
a: The first dictionary.
b: The second dictionary.
Returns:
The merged dictionary.
"""
def merge_recurse(a, b, path=None):
# Recurse on dict entries' values
if isinstance(a, dict) and isinstance(b, dict):
# Compute set of all keys in both dictionaries
keys = sorted(set(a.keys()) | set(b.keys()))
# Build output dictionary, merging values with common keys recursively
return { k: merge_recurse(a.get(k), b.get(k), path + [k]) for k in keys }
# Append array values by default
elif isinstance(a, (list, tuple)) and isinstance(b, (list, tuple)):
return a + b
# Override values of `a` with `b` if present in both
else: return a if b is None else b
# Merge dictionaries
return merge_recurse(a, b, path=[])
__all__ = [
# Functions (5)
"flatten_dict",
"nested_get",
"nested_set",
"nested_del",
"merge_dict"
]