0

I have a large dictionary (originally a json), something like this:

dictionary = {
    "Thing1": {
        "Thing1.1": {
            "Thing1.1.1": "Text",
            "Thing1.1.2": "Text",
            "Thing1.1.3": "Text"},
        "Thing1.2": [{
            "Thing1.2.1.1": "Text",
            "Thing1.2.1.2": "Text",
            "Thing1.2.1.3": "Text"},
            {
            "Thing1.2.2.1": "Text",
            "Thing1.2.2.2": "Text",
            "Thing1.2.2.3": "Text we're interested in"}
        ],
        "Thing1.3": {
            "Thing1.3.1": "Text",
            "Thing1.3.2": "Text",
            "Thing1.3.3": "Text"}
    },
    "Thing2": {
            "Thing2.1": {
                "Thing2.1.1": "Text",
                "Thing2.1.2": "Text",
                "Thing2.1.3": "Text"},
            "Thing2.2": {
                "Thing2.2.1": "Text",
                "Thing2.2.2": "Text",
                "Thing2.2.3": "Text"},
            "Thing2.3": {
                "Thing2.3.1": "Text",
                "Thing2.3.2": "Text",
                "Thing2.3.3": "Text"}
        },
    "Thing3": {
        "Thing3.1": {
            "Thing3.1.1": "Text",
            "Thing3.1.2": "Text",
            "Thing3.1.3": "Text"},
        "Thing3.2": {
            "Thing3.2.1": "Text",
            "Thing3.2.2": "Text",
            "Thing3.2.3": "Text"},
        "Thing3.3": {
            "Thing3.3.1": "Text",
            "Thing3.3.2": "Text",
            "Thing3.3.3": "Text"}
    }
}

And, having automated some things, I have some keys the values of which I'm interested in, in a string as such: key = '["Thing1"]["Thing1.2"][1]["Thing1.2.2.3"]'' and I'd like to be able to call the value for which I have the key as if it was the following: value = dictionary["Thing1"]["Thing1.2"][1]["Thing1.2.2.3"]

I have tried using exec, and formatting everything neatly using f-strings, however that's horrible and it does give me some trouble of its own, especially if I use it in functions due to the way it handles global and local variables.

Edit: I've changed the dictionary and key to represent what it looks more like in the real case: This is a dictionary of arbitrary depth that may contain lists embedded within it e.g. 'Thing1.2'.

Daniel
  • 3
  • 3
  • So are you trying to say "I want 1.2.3" and have the code correctly get dictionary["Thing1"]["Thing1.2"]["Thing1.2.3"] ? or something else? – PirateNinjas Sep 05 '22 at 14:23
  • I want `dictionary["Thing1"]["Thing1.2"]["Thing1.2.3"]` and already have code that gives me the string `key1 = '["Thing1"]["Thing1.2"]["Thing1.2.3"]'`. In other words, I want the output `"Text that we're interested in"` – Daniel Sep 05 '22 at 14:34
  • Can you share the code you're trying to use? It looks like `exec` or `eval` is what you're looking for. See https://stackoverflow.com/a/701813/16662168 – Andrew Sep 05 '22 at 14:46

3 Answers3

0

If you want to give "1.2.3" and get the item you want from the list you can use this code:

keys = "1.2.3".split(".") 
value = dictionary["Thing" + keys[0]]["Thing" + keys[0] + "." + keys[1]]["Thing" + keys[0] + "." + keys[1] + "." + keys[2]]

If you want, you can get it from your key1 variable:

key1 = '["Thing1"]["Thing1.2"]["Thing1.2.3"]'
keys = key1[key1.rfind("Thing") + 5: -2].split(".") 
value = dictionary["Thing" + keys[0]]["Thing" + keys[0] + "." + keys[1]]["Thing" + keys[0] + "." + keys[1] + "." + keys[2]]
Happy Ahmad
  • 1,072
  • 2
  • 14
  • 33
  • I already have the full key, as one would write it in code (see string key1). But now I want to use said key as if it were code, hence my use of exec, however I'd rather do it differently than to use exec. – Daniel Sep 05 '22 at 14:39
0

As I understand the question, you will always receive the three keys in a string as you showed above, if not please correct me. In that case, I have find the following solution:

text = '["Thing1"]["Thing1.2"]["Thing1.2.3"]'
keys = [eval(i) for i in text.replace("[", "").split("]")[:-1]]
target = dictionary[keys[0]][keys[1]][keys[2]] 

Being the variables:

keys = ['Thing1', 'Thing1.2', 'Thing1.2.3']
target = "Text we're interested in"

Edit: You can also do this if you don´t want to use exec or eval:

keys = text.replace("[", "").replace('"', "").split("]")[:-1]
target = dictionary[keys[0]][keys[1]][keys[2]] 

If u have a variable depth, you can replace the last lines with:

target = dictionary
for k in keys:
    target = target[k]
  • Yes! the last two lines are basically what I needed, can't believe I hadn't thought of it before.Thanks. However, if we don't know the depth of the dictionary, how would we go around doing that? After all writing `target = dictionary[keys[0]][keys[1]][keys[2]] ` will only work if we have a depth of 3 and only 3 – Daniel Sep 05 '22 at 14:55
  • @Daniel Arbitrary depth is handled in my answer – DarkKnight Sep 05 '22 at 14:59
  • Exactly, you can handle depth with a simple loop as @Vlad suggested. Sorry for that, I understood that the depth was constantly the same. I just included it in the answer. – Ander Gurtubay Sep 05 '22 at 15:04
  • Thanks to both of you. How would I go around dealing with the dictionaries having lists embedded within them, since I believe @Vlad 's solution's regex only deals with strings but not ints. In other words, the full key may also have ints in them like such `key = '["Thing1"]["Thing1.3"][0]["Thing1.3.1"]'` – Daniel Sep 06 '22 at 07:08
  • @Daniel See my revised answer – DarkKnight Sep 06 '22 at 07:36
0

Let's assume that the depth of the nested dictionaries is unknown. In that case we need a loop to navigate it. You can use a regular expression to isolate the embedded keys. Something like this:

import re

dictionary = {
    "Thing1": {
        "Thing1.1": {
            "Thing1.1.1": "Text",
            "Thing1.1.2": "Text",
            "Thing1.1.3": "Text"},
        "Thing1.2": [{
            "Thing1.2.1.1": "Text",
            "Thing1.2.1.2": "Text",
            "Thing1.2.1.3": "Text"},
            {
            "Thing1.2.2.1": "Text",
            "Thing1.2.2.2": "Text",
            "Thing1.2.2.3": "Text we're interested in"}
        ],
        "Thing1.3": {
            "Thing1.3.1": "Text",
            "Thing1.3.2": "Text",
            "Thing1.3.3": "Text"}
    },
    "Thing2": {
        "Thing2.1": {
            "Thing2.1.1": "Text",
            "Thing2.1.2": "Text",
            "Thing2.1.3": "Text"},
        "Thing2.2": {
            "Thing2.2.1": "Text",
            "Thing2.2.2": "Text",
            "Thing2.2.3": "Text"},
        "Thing2.3": {
            "Thing2.3.1": "Text",
            "Thing2.3.2": "Text",
            "Thing2.3.3": "Text"}
    },
    "Thing3": {
        "Thing3.1": {
            "Thing3.1.1": "Text",
            "Thing3.1.2": "Text",
            "Thing3.1.3": "Text"},
        "Thing3.2": {
            "Thing3.2.1": "Text",
            "Thing3.2.2": "Text",
            "Thing3.2.3": "Text"},
        "Thing3.3": {
            "Thing3.3.1": "Text",
            "Thing3.3.2": "Text",
            "Thing3.3.3": "Text"}
    }
}

key = '["Thing1"]["Thing1.2"][1]["Thing1.2.2.3"]'

list_ = re.findall(r'\["*(.*?)"*\]', key)

d = dictionary

for k in list_:
    if k.isdigit() and isinstance(d, list):
        d = d[int(k)]
    else:
        d = d.get(k)
    if d is None:
        break

print(d)

Output:

Text we're interested in
DarkKnight
  • 19,739
  • 3
  • 6
  • 22