简体   繁体   中英

Python nested dictionary append new key based on another list

I have a nested dictionary and I want to push a new element on in based on the resulted list. My current code is working fine, but I want a generic solution of multilevel dictionary and multiple nested list elements. Basically, I want to make this portion dynamic based on the list path- NB: The items on the list is dynamic

data['top_banner']['image']['global_image'] <---- path wil come from list element

Part of dictionary:

{
  "top_banner": {
    "image": {
      "global_image": {
        "alt": " ",
        "src": "path/to/file/5473cd34fadbb6a3-dine-1.jpg",
        "sizes": [
          {
            "src": "",
            "view": "100vw",
            "media": "(min-width:100px)",
            "intrinsicwidth": 0,
            "intrinsicheight": 0
          }
        ],
        "types": [
          "webp ",
          "jpg"
        ],
        "background": True,
        "intrinsicWidth": 2400,
        "intrinsicHeight": 2400
      }
    },
    "title": "Dine",
    "description": ""
  }
}

List path:

[['top_banner', 'image', 'global_image']]

Current code:

data['top_banner']['image']['global_image'].update({'intrinsicWidth': 200, 'intrinsicHeight': 300})

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this should be from the list 

If I understood correctly, use functools.reduce to go down the nested path

from functools import reduce
import pprint

data = {
    "top_banner": {
        "image": {
            "global_image": {
                "alt": " ", "src": "path/to/file/5473cd34fadbb6a3-dine-1.jpg",
                "sizes": [
                    {
                        "src": "", "view": "100vw", "media": "(min-width:100px)",
                        "intrinsicwidth": 0, "intrinsicheight": 0
                    }
                ],
                "types": ["webp ", "jpg"],
                "background": True, "intrinsicWidth": 2400, "intrinsicHeight": 2400
            }
        },
        "title": "Dine", "description": ""
    }
}

path = ['top_banner', 'image', 'global_image']
reduce(lambda y, x: y[x], path, data).update({'intrinsicWidth': 200, 'intrinsicHeight': 300})
pprint.pprint(data)

Output

{'top_banner': {'description': '',
                'image': {'global_image': {'alt': ' ',
                                           'background': True,
                                           'intrinsicHeight': 300,
                                           'intrinsicWidth': 200,
                                           'sizes': [{'intrinsicheight': 0,
                                                      'intrinsicwidth': 0,
                                                      'media': '(min-width:100px)',
                                                      'src': '',
                                                      'view': '100vw'}],
                                           'src': 'path/to/file/5473cd34fadbb6a3-dine-1.jpg',
                                           'types': ['webp ', 'jpg']}},
                'title': 'Dine'}}
import types

data = {
    "top_banner": {
        "image": {
            "global_image": {
                "alt": " ",
                "src": "path/to/file/5473cd34fadbb6a3-dine-1.jpg",
                "sizes": [
                    {
                        "src": "",
                        "view": "100vw",
                        "media": "(min-width:100px)",
                        "intrinsicwidth": 0,
                        "intrinsicheight": 0,
                    }
                ],
                "types": ["webp ", "jpg"],
                "background": True,
                "intrinsicWidth": 2400,
                "intrinsicHeight": 2400,
            }
        },
        "title": "Dine",
        "description": "",
    }
}

paths = [["top_banner", "image", "global_image"]]


def recursive(data, path):
    if len(path) == 0:
        return data
    return recursive(data[path[0]], path[1:])


for path in paths:
    recursive(data, path).update({"intrinsicWidth": 200, "intrinsicHeight": 300})

print(data)

result:

{
    "top_banner": {
        "image": {
            "global_image": {
                "alt": " ",
                "src": "path/to/file/5473cd34fadbb6a3-dine-1.jpg",
                "sizes": [
                    {
                        "src": "",
                        "view": "100vw",
                        "media": "(min-width:100px)",
                        "intrinsicwidth": 0,
                        "intrinsicheight": 0,
                    }
                ],
                "types": ["webp ", "jpg"],
                "background": True,
                "intrinsicWidth": 200,
                "intrinsicHeight": 300,
            }
        },
        "title": "Dine",
        "description": "",
    }
}

you can use a recursive function:

def get_dict_element_by_path(dic, lst):
    if lst:
        return get_dict_element_by_path(dic[lst[0]], lst[1:])
    else:
        return dic

my_list = ['top_banner', 'image', 'global_image']
get_dict_element_by_path(data, my_list).update({'intrinsicWidth': 200, 'intrinsicHeight': 300})

Or you can do it this way whitout recursion

data = {
    "top_banner": {
        "image": {
            "global_image": {
                "alt": " ",
                "src": "path/to/file/5473cd34fadbb6a3-dine-1.jpg",
                "sizes": [
                    {
                        "src": "",
                        "view": "100vw",
                        "media": "(min-width:100px)",
                        "intrinsicwidth": 0,
                        "intrinsicheight": 0,
                    }
                ],
                "types": ["webp ", "jpg"],
                "background": True,
                "intrinsicWidth": 2400,
                "intrinsicHeight": 2400,
            }
        },
        "title": "Dine",
        "description": "",
    }
}

paths = [["top_banner", "image", "global_image"]]
res = data

for path in paths:
    for p in path:
        res = res[p]
    res.update({"intrinsicWidth": 200, "intrinsicHeight": 300})


print(data)

and this is the result

{
    "top_banner": {
        "image": {
            "global_image": {
                "alt": " ",
                "src": "path/to/file/5473cd34fadbb6a3-dine-1.jpg",
                "sizes": [
                    {
                        "src": "",
                        "view": "100vw",
                        "media": "(min-width:100px)",
                        "intrinsicwidth": 0,
                        "intrinsicheight": 0,
                    }
                ],
                "types": ["webp ", "jpg"],
                "background": True,
                "intrinsicWidth": 200,
                "intrinsicHeight": 300,
            }
        },
        "title": "Dine",
        "description": "",
    }
}

You could make a nestedDict class based on dict and override its get, getitem, and setitem methods to support indexing by paths. This would make the indexing and manipulation more seamless:

class nestedDict(dict):

    def __getDictKey(self,index):
        parent,path = self,[index]
        while True:
            key = path.pop(0)            
            if isinstance(key,(list,tuple)): path[:0] = key
            elif path: parent = parent[key]
            else: break
        return parent,key
        
    def __getitem__(self,index):
        d,k = self.__getDictKey(index)
        return dict.__getitem__(d,k)
    
    def get(self,index,default=None):
        try: return self[index]
        except KeyError: return default

    def __setitem__(self,index,value):
        d,k = self.__getDictKey(index)
        dict.__setitem__(d,k,value)

Example usage:

data = nestedDict(data)

# Indexing by path:

data['top_banner', 'image','global_image','src']
'path/to/file/5473cd34fadbb6a3-dine-1.jpg'

data['top_banner', 'image','global_image','background'] = False

# Indexing by dynamic paths:

path = ['top_banner', 'image','global_image']

data[path]
{'alt':' ', 'src':'path/to/file/5473cd34fadbb6a3-dine-1.jpg', 'sizes': ... }

data[path,"types"]
['webp ', 'jpg']

data[path].update({'intrinsicwidth': 200, 'intrinsicheight': 300})

data[path,"types"].append('pdf')

data[path,'background'] = False

# Indexing by composite paths:

pathParts = [['top_banner', 'image'],['global_image','types']]

data[pathParts]
['webp ', 'jpg']

To perform the same update on a list of different paths, you should use a loop:

paths   = [path1,path2,path3]
newSize = {'intrinsicwidth': 200, 'intrinsicheight': 300}
for path in paths: 
    data[path,"global_image"].update(newSize)

Eventually, you may also want to override the keys, values and items method to allow iteration over paths of various lengths:

def keys(self,depth=1):
    if depth ==1 :
        yield from ([k] for k in dict.keys(self))
    else:
        for k,d in dict.items(self):
            if not isinstance(d,dict): continue
            yield from ([k]+p for p in nestedDict.keys(d,depth-1))

def values(self,depth=1):
    yield from (self[k] for k in self.keys(depth))

def items(self,depth=1):
    return zip(self.keys(depth),self.values(depth)))

Which will allow things such as:

# get all 2-part paths

for p in data.keys(2): print(p)

['top_banner', 'image']
['top_banner', 'title']
['top_banner', 'description']

# all level 4 paths and values

for k,v in data.items(4):print(k,":",v)

['top_banner', 'image', 'global_image', 'alt'] :  
['top_banner', 'image', 'global_image', 'src'] : path/to/file/5473cd34fadbb6a3-dine-1.jpg
['top_banner', 'image', 'global_image', 'sizes'] : [{'src': '', 'view': '100vw', 'media': '(min-width:100px)', 'intrinsicwidth': 0, 'intrinsicheight': 0}]
['top_banner', 'image', 'global_image', 'types'] : ['webp ', 'jpg']
['top_banner', 'image', 'global_image', 'background'] : True
['top_banner', 'image', 'global_image', 'intrinsicWidth'] : 2400
['top_banner', 'image', 'global_image', 'intrinsicHeight'] : 2400

# All 'types' values (at level 3)

for v in data.values(3):print(v['types'])

['webp ', 'jpg']

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM