How dynamically set value to complex json content, receiving key (or key path) and value as parameters

Viewed 87

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'
2 Answers

Parse the path and define an array of keys to traverse. Then recursively find the last key and set the value.

import re

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}]}}

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}]}}
Related