Reputation: 38502
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- N.B: 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
Upvotes: 1
Views: 85
Reputation: 42143
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']
Upvotes: 1
Reputation: 850
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": "",
}
}
Upvotes: 1
Reputation: 850
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": "",
}
}
Upvotes: 2
Reputation: 61910
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'}}
Upvotes: 3
Reputation: 12701
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})
Upvotes: 1