Based on Cloudkollektiv 's excellent idea, I've refactored the code which basically can do the same as the original functions, but all in one.
I've also added the possibility, instead of replacing an orignal string by a replacement string, to replace every value by fn(value) where fn is a function given as argument to original.
[EDIT] I've improved the code so it retains the initial object type, which comes in handy when dealing with structs like CommentedMap from ruamel.yaml
I've also added an option to replace all values by fn(key, value) for dicts where fn is a function given as argument to original.
[/EDIT]
def replace_in_iterable(
src: Union[dict, list],
original: Union[str, Callable],
replacement: Any = None,
callable_wants_key: bool = False,
):
"""
Recursive replace data in a struct
Replaces every instance of string original with string replacement in a list/dict
If original is a callable function, it will replace every value with original(value)
If original is a callable function and callable_wants_key == True,
it will replace every value with original(key, value) for dicts
and with original(value) for any other data types
"""
def _replace_in_iterable(key, _src):
if isinstance(_src, dict) or isinstance(_src, list):
_src = replace_in_iterable(_src, original, replacement, callable_wants_key)
elif isinstance(original, Callable):
if callable_wants_key:
_src = original(key, _src)
else:
_src = original(_src)
elif isinstance(_src, str) and isinstance(replacement, str):
_src = _src.replace(original, replacement)
else:
_src = replacement
return _src
if isinstance(src, dict):
for key, value in src.items():
src[key] = _replace_in_iterable(key, value)
elif isinstance(src, list):
result = []
for entry in src:
result.append(_replace_in_iterable(None, entry))
src = result
else:
src = _replace_in_iterable(None, src)
return src
TL;DR: You can directly use this function as package via pip with:
pip install ofunctions.misc
Then use it with
from ofunctions.misc import replace_in_iterable
def test(string):
return f"-{string}-"
output = replace_in_iterable(input, test)
Input
input = {
'key1': 'a string',
'key2': 'another string',
'key3': [
'a string',
'another string',
[1, 2, 3],
{
'key1': 'a string',
'key2': 'another string'
}
],
'key4': {
'key1': 'a string',
'key2': 'another string',
'key3': [
'a string',
'another string',
500,
1000
]
},
'key5': {
'key1': [
{
'key1': 'a string'
}
]
}
}
Output
input = {
'key1': '-a string-',
'key2': '-another string-',
'key3': [
'-a string-',
'-another string-',
['-1-', '-2-', '-3-'],
{
'key1': '-a string-',
'key2': '-another string-'
}
],
'key4': {
'key1': '-a string-',
'key2': '-another string-',
'key3': [
'-a string-',
'-another string-',
'-500-',
'-1000-'
]
},
'key5': {
'key1': [
{
'key1': '-a string-'
}
]
}
}
Of course the original syntax via output = replace_in_iterable(input, "string", "something) still works.