Created
October 18, 2023 11:20
-
-
Save eladn/13f25bbee32a73448d6700974ad25a6f to your computer and use it in GitHub Desktop.
Python functions for accessing a nested dictionary by a dot-separated key for reading and setting values and creating missing dictionaries in nested levels.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| __author__ = "Elad Nachmias" | |
| __email__ = "[email protected]" | |
| __date__ = "2023-10-18" | |
| from typing import Dict, Any | |
| def access_nested_dict_by_dot_key( | |
| dct: Dict, key: str, create_missing: bool = False, replace_nones: bool = False) -> Any: | |
| """ | |
| Access a nested dict in the given input dict by accessing a sequence of keys (given as a single string in which the | |
| sub-keys are separated by dots) one after the other. If one of the keys in the sequence doesn't exist (and/or is | |
| None) it can be created as a dict. For example, for key='k1.k2.k3' it would return dct['k1']['k2']['k3']. | |
| :param dct: the root level of the input dictionary to be accessed | |
| :param key: sequence of keys given as a single string where the keys are separated by dots | |
| :param create_missing: if True, for each non-existing sub-key an empty dict would be appended in its parent under | |
| that sub-key | |
| :param replace_nones: if True, for each sub-key that is mapped to None at its parent dict, the None value would be | |
| replaced with a new empty dictionary. | |
| """ | |
| sub_keys = key.split('.') | |
| dict_type = dct.__class__ | |
| # Iterate over all keys except the last one and make sure they're all nested dictionaries. | |
| for sub_key_idx, sub_key in enumerate(sub_keys): | |
| if sub_key not in dct: | |
| if not create_missing: | |
| return None | |
| sub_dict = dct[sub_key] = dict_type() | |
| else: | |
| sub_dict = dct[sub_key] | |
| if sub_dict is None: | |
| if not replace_nones: | |
| return None | |
| sub_dict = dct[sub_key] = dict_type() | |
| if not isinstance(sub_dict, (dict, dict_type)): | |
| raise ValueError( | |
| f"Key prefix `{'.'.join(sub_keys[:sub_key_idx + 1])}` already exists " | |
| f"with non dict type `{type(sub_dict)}`.") | |
| dct = sub_dict | |
| return dct | |
| def get_or_set_nested_dict_value_by_dot_key( | |
| dct: Dict, key: str, default_value: Any, | |
| replace_none_value: bool = True, replace_none_parents: bool = True) -> Any: | |
| """ | |
| Access a nested dict in the given input dict by accessing a sequence of keys (given as a single string in which the | |
| sub-keys are separated by dots) one after the other. If one of the keys in the sequence doesn't exist (and/or is | |
| None) it can be created as a dict. For example, for key='k1.k2.k3' it would return dct['k1']['k2']['k3']. | |
| Additionally, if the last sub-key doesn't exist in its corresponding parent dictionary, it would be added to this | |
| dict with the given default value. | |
| :param dct: the root level of the input dictionary to be accessed | |
| :param key: sequence of keys given as a single string where the keys are separated by dots | |
| :param default_value: a value to be assigned to the record of the last sub-key if it doesn't already set | |
| :param replace_none_value: if True, override the entry of the last sub-key (in its corresponding parent dictionary) | |
| with the given default value | |
| :param replace_none_parents: if True, for each sub-key (except the last one) that is mapped to None at its | |
| corresponding parent dict, it would be replaced with a new empty dictionary. | |
| """ | |
| sub_keys = key.split('.') | |
| last_sub_key = sub_keys[-1] | |
| direct_parent_dict_key = '.'.join(sub_keys[:-1]) | |
| direct_parent_dict = access_nested_dict_by_dot_key( | |
| dct=dct, key=direct_parent_dict_key, create_missing=True, replace_nones=replace_none_parents) | |
| if last_sub_key not in direct_parent_dict or (replace_none_value and direct_parent_dict[last_sub_key] is None): | |
| direct_parent_dict[last_sub_key] = default_value | |
| return direct_parent_dict[last_sub_key] | |
| def set_nested_dict_value_by_dot_key( | |
| dct: Dict, key: str, value: Any, | |
| create_missing_parents: bool = False, replace_none_parents: bool = False): | |
| """ | |
| Assign a value under for a given nested key in the given input nested dict, where the given key is a sequence of | |
| sub-keys (given as a single string in which the sub-keys are separated by dots). For example, for key='k1.k2.k3' | |
| it would assign dct['k1']['k2']['k3'] := value. | |
| :param dct: the root level of the input dictionary to be accessed and/or updated | |
| :param key: sequence of keys given as a single string where the keys are separated by dots | |
| :param value: a value to be assigned to the record of the last sub-key | |
| :param create_missing_parents: if True, for each non-existing sub-key (except the last one) an empty dict would be | |
| appended in its parent under that sub-key | |
| :param replace_none_parents: if True, for each sub-key (except the last one) that is mapped to None at its parent | |
| dict, the None value would be replaced with a new empty dictionary | |
| """ | |
| sub_keys = key.split('.') | |
| last_sub_key = sub_keys[-1] | |
| direct_parent_dict_key = '.'.join(sub_keys[:-1]) | |
| direct_parent_dict = access_nested_dict_by_dot_key( | |
| dct=dct, | |
| key=direct_parent_dict_key, | |
| create_missing=create_missing_parents, | |
| replace_nones=replace_none_parents) | |
| direct_parent_dict[last_sub_key] = value |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment