As json example, I have this:
{
"id": "foo",
"items": [
{
"id": "aaa",
"colour": "blue"
},
{
"id": "bbb",
"colour": "red",
}
]
}
Once json is read in Python into a dictionary:
What I want to do is a function that receives field to fill and value to be set For example:
func(json_dict, 'items[0].colour', 'green')
or
func(json_dict, 'id', 'bar')
And the challenge is to do this for whatever json with whatever deep I have. But I don't know how dynamically I can do:
json_dict['items'][0]['colour'] = 'green'
json_dict['id'] = 'bar'
CodePudding user response:
Parse the path and define an array of keys to traverse. Then recursively find the last key and set the value.
def set_val(json_obj, path, val):
path_arr = []
for _, k, i in re.findall(r'((\w )(\[\d \])?\.?)', path):
path_arr.append(k)
if i: path_arr.append(int(i[1:-1]))
def _get(c_path, curr=json_obj):
if len(c_path) == 1: curr[c_path[0]] = val
else: _get(c_path[1:], curr[c_path[0]])
_get(path_arr)
my_json = {'a': {'b': [1, 2, {'x': 2}]}}
set_val(my_json, 'a.b[2].x', 5)
print(my_json) # ==> {'a': {'b': [1, 2, {'x': 5}]}}
CodePudding user response:
Here's one alternative to a regex approach. However, a minor disadvantage is you can't use a 'a.b[2].x'
syntax anymore.
from typing import Tuple, Union
def set_val(d, path, val):
*paths, last_path = _get_dot_path(path)
path_dict = d
try:
for i, p in enumerate(paths):
path_dict = path_dict[p]
path_dict[last_path] = val
except (KeyError, IndexError, TypeError):
# TODO use variables i and p, if needed
raise
def _get_dot_path(path: str) -> Tuple[Union[str, int], ...]:
return tuple([int(s) if s.lstrip('-').isdecimal() else s
for s in path.split('.')])
my_json = {'a': {'b': [1, 2, {'x': 2}]}}
set_val(my_json, 'a.b.2.x', 5)
print(my_json) # {'a': {'b': [1, 2, {'x': 5}]}}