How to update values in a nested dictionary?

Viewed 64

I have 2 dictionaries:

data = {
  "filter":
    {
      "and":
        [
          {
            "or":
              [
                {
                  "and":
                    [
                      {"category": "profile", "key": "languages", "operator": "IN", "value": "EN"},
                      {"category": "skill", "key": "26366", "value": 100, "operator": "EQ"},
                    ],
                },
              ],
          },
          {"or": [{"category": "skill", "key": "45165", "operator": "NE"}]},
          {"or": [{"category": "skill", "key": "48834", "value": 80, "operator": "GT"}]},
          {"or": [{"category": "profile", "key": "gender", "operator": "EQ", "value": "FEMALE"}]},
        ],
    },
}

new_val = {'26366': '11616', '45165': '11613', '48834': '11618'}

I want to update values in "data" dictionary with the values from "new_val" dictionary.

So that 26366(in "data" dict) becomes 11616(from "new_val" dict), 45165 becomes 11613, and 48834 becomes 11618. "data" dictionary nesting can be different (both up and down)

The key in the "data" dictionary can be different, not only "key", it can be "skill_id", "filter_id" and so on.

And get this result:

{
  "filter":
    {
      "and":
        [
          {
            "or":
              [
                {
                  "and":
                    [
                      {"category": "profile", "key": "languages", "operator": "IN", "value": "EN"},
                      {"category": "skill", "key": "11616", "value": 100, "operator": "EQ"},
                    ],
                },
              ],
          },
          {"or": [{"category": "skill", "key": "11613", "operator": "NE"}]},
          {"or": [{"category": "skill", "key": "11618", "value": 80, "operator": "GT"}]},
          {"or": [{"category": "profile", "key": "gender", "operator": "EQ", "value": "FEMALE"}]},
        ],
    },
}
6 Answers

To return an updated dict without modifying the old one:

def updated_in_depth(d, replace):
    if isinstance(d, dict):
        return {k: updated_in_depth(v, replace)
                for k,v in d.items()}
    elif isinstance(d, list):
        return [updated_in_depth(x, replace) for x in d]
    else:
        return replace.get(d, d)

Testing with your data and new_val:

>>> updated_in_depth(data, new_val)
{'filter': {'and': [{'or': [{'and': [
                            {'category': 'profile', 'key': 'languages', 'operator': 'IN', 'value': 'EN'},
                            {'category': 'skill', 'key': '11616', 'value': 100, 'operator': 'EQ'}]}]},
                    {'or': [{'category': 'skill', 'key': '11613', 'operator': 'NE'}]},
                    {'or': [{'category': 'skill', 'key': '11618', 'value': 80, 'operator': 'GT'}]},
                    {'or': [{'category': 'profile', 'key': 'gender', 'operator': 'EQ', 'value': 'FEMALE'}]}]}}

Use something like this:

data['filter']['and']['or']['and'][1]['key']='11616'

To search for the keys recursively you can do:

from copy import deepcopy


def replace(d, new_vals):
    if isinstance(d, dict):
        # replace key (if there's match):
        if "key" in d:
            d["key"] = new_vals.get(d["key"], d["key"])
        for v in d.values():
            replace(v, new_vals)
    elif isinstance(d, list):
        for v in d:
            replace(v, new_vals)


new_data = deepcopy(data)
replace(new_data, new_val)
print(new_data)

Prints:

{
    "filter": {
        "and": [
            {
                "or": [
                    {
                        "and": [
                            {
                                "category": "profile",
                                "key": "languages",
                                "operator": "IN",
                                "value": "EN",
                            },
                            {
                                "category": "skill",
                                "key": "11616",
                                "value": 100,
                                "operator": "EQ",
                            },
                        ]
                    }
                ]
            },
            {"or": [{"category": "skill", "key": "11613", "operator": "NE"}]},
            {
                "or": [
                    {
                        "category": "skill",
                        "key": "11618",
                        "value": 80,
                        "operator": "GT",
                    }
                ]
            },
            {
                "or": [
                    {
                        "category": "profile",
                        "key": "gender",
                        "operator": "EQ",
                        "value": "FEMALE",
                    }
                ]
            },
        ]
    }
}

If you don't need copy of data you can omit the deepcopy:

replace(data, new_val)
print(data)

You can build a recursive function like this

def walk_dict(d):
    if isinstance(d, list):
        for item in d:
            walk_dict(item)
    elif isinstance(d, dict):
        if 'key' in d and d['key'] in new_val:
            d['key'] = new_val[d['key']]
        for k, v in d.items():
            walk_dict(v)


walk_dict(data)
print(data)

As many have advised, a recursive function will do the trick:

def a(d):
    if isinstance(d, dict): # if dictionary, apply a to all values
        d = {k: a(d[k]) for k in d.keys()}
        return d
    elif isinstance(d, list): # if list, apply to all elements
        return [a(x) for x in d]
    else: # apply to d directly (it is a number, a string or a bool)
        return new_val[d] if d in new_val else d

When a is called, it check what is the type of the variable d:

  • if d is a list, it apply a to each element of the list and return the updated list
  • if d is a dict, it applies a to all values and return the updated dict
  • otherwise, it returns the mapped new value if the old one has been found in the new_val keys
data = {
  "filter":
    {
      "and":
        [
          {
            "or":
              [
                {
                  "and":
                    [
                      {"category": "profile", "key": "languages", "operator": "IN", "value": "EN"},
                       {"category": "skill", "key": "11616", "value": 100, "operator": "EQ"},
                    ],
                },
              ],
          },
          {"or": [{"category": "skill", "key": "11613", "operator": "NE"}]},
          {"or": [{"category": "skill", "key": "11618", "value": 80, "operator": "GT"}]},
          {"or": [{"category": "profile", "key": "gender", "operator": "EQ", "value": "FEMALE"}]},
        ],
        },
}
class Replace:
    def __init__(self,data):
        self.data=data
    def start(self,d):
        data = self.data
    
        
        def replace(data):
            if type(data) == list:
                for v in data:
                    replace(v)
            if type(data) == dict:
                for k,v in data.items():
                    if type(v) == dict:
                        replace(v)
                    if type(v) == str:
                        if v in d:
                            data[k] = d[v]
    
        replace(data)
        return data
new_data = Replace(data).start({'26366': '11616',
                                '45165': '11613',
                                '48834': '11618'})
print(new_data)


    
            
    


    
Related