1

I have a JSON object, data, I need to modify.

Right now, I am modifying the object as follows:

data['Foo']['Bar'] = 'ExampleString'

Is it possible to use a string variable to do the indexing?

s = 'Foo/Bar'
data[s.split('/')] = 'ExampleString'

The above code does not work. How can I achieve the behavior I am after?


NB : I am looking for a solution which supports arbitrary number of key "levels", for instance the string variable may be Foo/Bar/Baz, or Foo/Bar/Baz/Foo/Bar, which would correspond to data['Foo']['Bar']['Baz'] and data['Foo']['Bar']['Baz']['Foo']['Bar'].

7
  • Yes, you can do x = s.split('/');data[x[0]][x[1]]? Commented Feb 3, 2020 at 11:27
  • @shaikmoeed Yes, but what if the number of elements is variable? E.g. Foo/Bar/Baz Commented Feb 3, 2020 at 11:28
  • does your string get a bit complex? I mean like 'Foo/Bar/inner/first/almost/here'? Commented Feb 3, 2020 at 11:29
  • @Ramesh Yes, the solution should supported arbitrary number of fields Commented Feb 3, 2020 at 11:29
  • Then, you should go with a loop until you reach the last element. Is that what you are looking for? Commented Feb 3, 2020 at 11:32

3 Answers 3

1

Without changing completely the data class you want to use, this might be easisest:

def jsonSetPath(jobj, path, item): 
    prev = None 
    y = jobj
    for x in path.split('/'):
        prev = y
        y = y[x]
    prev[x] = item

A wrapper Python to descend iteratively into the object. Then you can use

jsonSetPath(data, 'foo/obj', 3)

normally. You can add this functionality to your dictionary by inheriting dict if you prefer:

class JsonDict(dict):
    def __getitem__(self, path):
        # We only accept strings in this dictionary
        y = self
        for x in path.split('/'):
            y = dict.get(y, x)
        return y

    def __setitem__(self, path, item):
        # We only accept strings in this dictionary
        y = self
        prev = None
        for x in path.split('/'):
            prev = y
            y = dict.get(y, x)
        prev[x] = item

note using UserDict from collections may be advised, but seems to much of a hassle without converting all the inner dictionaries to user dictionaries. Now you wrap your data (data = JsonDict(data)) and use it as you wanted. If you want to use non-strings as your keys, you need to handle that (though I am not sure that makes sense in this specific dictionary implementation).

Note only the "outer" dictionary is your custom dictionary. If the use case is more advanced you would need to convert all the inner ones as well, and then you might as well use UserDictionary.

Sign up to request clarification or add additional context in comments.

6 Comments

Subclassing UserDict is an interesting idea. Then this behavior can be baked into __getitem__ so no special method is needed
@DeepSpace I am writing that asws :)
BTW, jsonGetPath will not work. You have to verify that you return the dict, otherwise you will return the value. ie you should only do y = y[x] if y[x] is a dict
@DeepSpace I don't care which I return. Why should I? If path to a value is given, it's on the caller, isn't it?
If you consider data to be {'Foo': {'Bar': {'Baz': None}}}, how would you use jsonGetPath to change None to 1?
|
1

A very naive solution to get you going in the correct direction.

You need to add error handling, for example what happens if somewhere a long the path a key is missing? You can either bail out or add a new dict on the fly.

def update(path, d, value):
    for nested_key in path.split('/'):
        temp = d[nested_key]
        if isinstance(temp, dict):
            d = d[nested_key]
    d[nested_key] = value

one_level_path = 'Foo/Bar'
one_level_dict = {'Foo': {'Bar': None}}

print(one_level_dict)
update(one_level_path, one_level_dict, 1)
print(one_level_dict)

two_level_path = 'Foo/Bar/Baz'
two_level_dict = {'Foo': {'Bar': {'Baz': None}}}

print(two_level_dict)
update(two_level_path, two_level_dict, 1)
print(two_level_dict)

Outputs

{'Foo': {'Bar': None}}
{'Foo': {'Bar': 1}}
{'Foo': {'Bar': {'Baz': None}}}
{'Foo': {'Bar': {'Baz': 1}}}

1 Comment

Thank you for your answer. I decided to go with jsonSetPath from @kabanus as it is slightly more readable in my opinion.
1

Using recursion:

x = {'foo': {'in':{'inner':9}}}
path = "foo/in/inner";

def setVal(obj,pathList,val):
    if len(pathList) == 1:
        obj[pathList[0]] = val
    else:
     return setVal(obj[pathList[0]],pathList[1:],val)

print(x)
setVal(x,path.split('/'),10)
print(x)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.