6

I want to replace the values (formated as strings) with the same values as integers, whenever the key is 'current_values'.

d = {'id': '10', 'datastreams': [{'current_value': '5'}, {'current_value': '4'}]}

Desired Output:

d = {'id': '10', 'datastreams': [{'current_value': 5}, {'current_value': 4}]}
6
  • 5
    What did you try so far? Commented Apr 16, 2019 at 9:23
  • The fact you are mixing list within dict could make it tricky. Commented Apr 16, 2019 at 9:29
  • 2
    @DirtyBit he's probably new to SO so scaring him off with negative reactions is not very inviting. Commented Apr 16, 2019 at 9:29
  • @JohnnyMcFly usually it is best to apply the principles defined in the MCVE guide by providing an example of the attempt you made to fix your issue. Keep in mind for the future. Commented Apr 16, 2019 at 9:33
  • 1
    sry folks, you are right, I will considers that in the future, thx! Commented Apr 16, 2019 at 9:36

8 Answers 8

26

The following piece of code replaces (substrings of) values in a dictionary. It works for nested json structures and copes with json, list and string types. You can easily add other types if needed.

def dict_replace_value(d: dict, old: str, new: str) -> dict:
    x = {}
    for k, v in d.items():
        if isinstance(v, dict):
            v = dict_replace_value(v, old, new)
        elif isinstance(v, list):
            v = list_replace_value(v, old, new)
        elif isinstance(v, str):
            v = v.replace(old, new)
        x[k] = v
    return x


def list_replace_value(l: list, old: str, new: str) -> list:
    x = []
    for e in l:
        if isinstance(e, list):
            e = list_replace_value(e, old, new)
        elif isinstance(e, dict):
            e = dict_replace_value(e, old, new)
        elif isinstance(e, str):
            e = e.replace(old, new)
        x.append(e)
    return x

# See input and output below
output = dict_replace_value(input, 'string', 'something')

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:

print(output)

{
   "key1":"a something",
   "key2":"another something",
   "key3":[
      "a something",
      "another something",
      [
         1,
         2,
         3
      ],
      {
         "key1":"a something",
         "key2":"another something"
      }
   ],
   "key4":{
      "key1":"a something",
      "key2":"another something",
      "key3":[
         "a something",
         "another something",
         500,
         1000
      ]
   },
   "key5":{
      "key1":[
         {
            "key1":"a something"
         }
      ]
   }
}
Sign up to request clarification or add additional context in comments.

4 Comments

Which is the MOST generic and re-usable answer and SHOULD be marked as the correct one !!!
I was walking into the same problem, and most of the answers did not go further than a one-level nested structured. So that is why I came up with the above answer.
@Nebulastic Great answer. Nobody ever goes deeper.
Great answer, made me think of an improvement, allowing to run functions instead of replacements. See below. Thanks.
5
d = {'id': '10', 'datastreams': [{'current_value': '5'}, {'current_value': '4'}]}

for elem in d['datastreams']:      # for each elem in the list datastreams
    for k,v in elem.items():       # for key,val in the elem of the list 
        if 'current_value' in k:   # if current_value is in the key
            elem[k] = int(v)       # Cast it to int
print(d)

OUTPUT:

{'id': '10', 'datastreams': [{'current_value': 5}, {'current_value': 4}]}

Comments

2

A general approach (assuming you don't know in advance which key of the dict is pointing to a list) would be to iterate over the dict and check the type of its values and then iterate again into each value if needed.

In your case, your dictionary may contain a list of dictionaries as values, so it is enough to check if a value is of type list, if so, iterate over the list and change the dicts you need.

It can be done recursively with a function like the following:

def f(d):
    for k,v in d.items():
        if k == 'current_value':
            d[k] = int(v)
        elif type(v) is list:
            for item in v:
                if type(item) is dict:
                    f(item)

>>> d = {'id': '10', 'datastreams': [{'current_value': '5'}, {'current_value': '4'}]}
>>> f(d)
>>> d
{'id': '10', 'datastreams': [{'current_value': 5}, {'current_value': 4}]}  

1 Comment

This is the easiest answer.
1

Can be done with list comprehension:

d['datastreams'] = [{'current_value': int(ds['current_value'])} if ('current_value' in ds) else ds for ds in d['datastreams']]

Comments

0

You can use ast.literal_eval to evaluate the underlying value for items with current_value key in the d['datastreams'] list. Then check whether the type is an int using isinstance for such values. Finally, type cast such values to int.

import ast
d = {'id': '10', 'datastreams': [{'current_value': '5'}, {'current_value': '4'}]}
for i in d['datastreams']:
    for k,v in i.items():
        if 'current_value' in k and isinstance(ast.literal_eval(v),int):
            i[k] = int(v)
#Output:
print(d)
{'id': '10', 'datastreams': [{'current_value': 5}, {'current_value': 4}]}

Comments

0

You could use this method which would loop through checks for current_value in list and change it to integer by passing the value through int() function:

for value in d.values():
    for element in value:
        if 'current_value' in element:
            element['current_value'] = int(element['current_value'])

Comments

0

Taking alec_djinn's solution little farther to handle also nested dicts:

def f(d):
    for k,v in d.items():
        if k == 'current_value':
            d[k] = int(v)
        elif type(v) is list:
            for item in v:
                if type(item) is dict:
                    f(item)
        if type(v) is dict:
            f(v)

3 Comments

It should already handle nested objects
It was only handled list nested objects but not dict nested objects.
I tried to run your code but it's not working since key is not defined
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.