0

I have a weird scenario where I need to convert the string like "data["data"]["name"][0]" into a dict variable which can be used to modify the dict variable value

so

input. - "data["data"]["name"][0]" and

output - data["data"]["name"][0]

so that I can use it modify the value of the variable.

Things that I have tried are locals() and globals() way of converting string to Variable but they seems to be failing given the string will actually be a complex dict variable.

update1:-

I was trying to get all the keys(path to keys) of a complex nested json and ended up getting that using collections and strings. so each key's path is string "data["data"]["name"][0]" but to modify the complex json I would need to interpret this as variable and assign value.

sample code

var = "data['data']['name'][0]"
data = {"data":{"name":["test","tst2"]}}
print(data['data']['name'][0])
print(var) # this prints the string
print(eval(var)) # this prints the value of string evaluted or like get method
#how to set value using var variable
#means var should be like data['data']['name'][0]
pkm
  • 2,683
  • 2
  • 29
  • 44
  • 2
    Can you share how you end up having to deal with something like this - it's really weird and probably this is XY problem. – buran Jan 09 '22 at 08:27
  • Put the top level into a dict variable you manage, rather than trying to add it to the locals or the globals? In most cases, you don't want to let an input file control variables directly in your program... – Jiří Baum Jan 09 '22 at 08:28
  • Would exec('data["data"]["name"][0]') be useful in your case? It converts string into python code and executes the line of code. You might want to give it a try. – Mahmoud Embaby Jan 09 '22 at 08:36
  • @buran updated the question with update1 – pkm Jan 09 '22 at 08:38
  • You can refer to [this](https://stackoverflow.com/questions/2371436/evaluating-a-mathematical-expression-in-a-string). – pppig Jan 09 '22 at 08:39
  • Can you post a sample input (and perhaps output), please? – Jiří Baum Jan 09 '22 at 08:45
  • @MahmoudEmbaby that's true but after doing exec I won't be able to use that and modify the value of variable. any thoughts around that? – pkm Jan 09 '22 at 08:45
  • You'll have to parse it and put it into some sort of data structure; don't try to make it directly set variables, that often leads to trouble – Jiří Baum Jan 09 '22 at 08:47
  • Also, if you have JSON, just load that instead? There's a function for that in the standard python library; `json.load` – Jiří Baum Jan 09 '22 at 08:48
  • 2
    `exec('data["data"]["name"][0] = "NEW DATA"')` this works but not sure if it is suitable in your case. – Mahmoud Embaby Jan 09 '22 at 08:51
  • @MahmoudEmbaby give a try with sample code shared , in snippet u shared the data["data"]["name"][0] is acting as variable though in my case it will be string – pkm Jan 09 '22 at 08:57
  • Probably best to parse `var` using either `parse_ast` or a regex, then navigating `data` based on that? – Jiří Baum Jan 09 '22 at 09:05
  • 1
    _I was trying to get all the keys(path to keys) of a complex nested json and ended up getting that using collections and strings._ this is so wrong. first of all why do you need to hardcore the path as collection of strings, second - instead of string like this why not e.g. tuple `('data', 'name', 0)` and then iterate over tuple elements to get the value you want. And this is just slightly better than using string - it's still not completely clear what you ultimately try to achieve. – buran Jan 09 '22 at 09:07
  • 1
    I would suggest that you ask completely different question, showing the JSON and explaining exactly what you want to do with it (expected output), instead of asking how to solve a problem you created yourself. This is XY problem. – buran Jan 09 '22 at 09:09
  • @buran instead of tuple I do get list [ 'data' , 'name' , 0 ] but it's not about reading the value . it's about modifying them. and to modify main dict based on path, I would need complete path for variable assignment – pkm Jan 09 '22 at 09:10
  • It's exactly the same - you can do assignment too – buran Jan 09 '22 at 09:11
  • Assuming that it is always the `data` you are accessing, you can split the `[, ]` and iteratively set the variable to the next `[...]` of it, until you have done it for all variables and reached the end. I.e. `c = data; ...; c = c['data']; c = c['name']; c = c[0]`, but for a more generic adaptation. Also, you might need to convert to `int` where applicable. EDIT: ignore the last key, and use it in the end to set the variable. `c[0] = ...` should modify it in-place, because it is [if executed properly], linked. – Larry the Llama Jan 09 '22 at 10:40

1 Answers1

1

It is not pretty, but it works :

def set_value(d, selector, val):
    """Follow the selector inside `d` to set the value of the last node."""
    if not selector.startswith("data"):
        raise ValueError("the query does not start with 'data'")

    # start parsing the selector
    parts = selector[len("data"):].split("]")
    if parts[-1] != "":
        raise ValueError("the query does not end with a ']'")
    parts.pop()  # remove the last part, we know it's empty
    current_node = d
    for part in parts[:-1]:  # for each part but not the last
        if (part.startswith("['") and part.endswith("'")) or \
                (part.startswith("[\"") and part.endswith("\"")):  # it's a dictionary access
            key_name = part[2:-1]
            current_node = current_node[key_name]
        elif part[1] in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
            index_val = int(part[1:])
            current_node = current_node[index_val]
        else:
            raise ValueError(f"the query part {part[:-1]!r} is syntactically incorrect")
    # then handle the last part
    last_part = parts[-1]
    if (last_part.startswith("['") and last_part.endswith("'")) or \
            (last_part.startswith("[\"") and last_part.endswith("\"")):  # it's a dictionary access
        key_name = last_part[2:-1]
        current_node[key_name] = val
    elif last_part[1] in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
        index_val = int(last_part[1:])
        current_node[index_val] = val
    else:
        raise ValueError(f"the query part {last_part!r} is syntactically incorrect")

    return d


data = {"data": {"name": ["test", "tst2"]}}

print(data)  # {'data': {'name': ['test', 'tst2']}}
data = set_value(data, 'data["data"]["name"][0]', 12)
print(data)  # {'data': {'name': [14, 'tst2']}}
data = set_value(data, "data['data']['name'][1]", -4)
print(data)  # {'data': {'name': [12, -4]}}
data = set_value(data, "data['foo']", {'bar': [5, 6]})
print(data)  # {'data': {'name': [12, -4]}, 'foo': {'bar': [5, 6]}}
data = set_value(data, "data['foo']['qux']", 'hh')
print(data)  # {'data': {'name': [12, -4]}, 'foo': {'bar': [5, 6], 'qux': 'hh'}}
Lenormju
  • 4,078
  • 2
  • 8
  • 22