-3

I have a string 'request.context.user_id' and I want to split the string by '.' and use each element in the list as a dictionary key. Is there a way to do this for lists of varying lengths without trying to hard code all the different possible list lengths after the split?

parts = string.split('.')
if len(parts)==1:
    data = [x for x in logData if x[parts[0]] in listX]
elif len(parts)==2:
    data = [x for x in logData if x[parts[0]][parts[1]] in listX]
else:
    print("Add more hard code")

listX is a list of string values that should be retrieved by x[parts[0]][parts[1] logData is a list obtained from reading a json file and then the list can be read into a dataframe using json_normalize... the df portion is provided to give some context about its structure.. a list of dicts:

import json
from pandas.io.json import json_normalize

with open(project_root+"filename") as f:
    logData = json.load(f)

df = json_normalize(logData)

2 Answers2

0

I just realized in the comments you said that you didn't want to create a new dictionary but access an existing one x via chaining up the parts in the list.

(3.b) use a for loop to get/set the value in the key the path

In case you want to only read the value at the end of the path in

import copy

def get_val(key_list, dict_):
    reduced = copy.deepcopy(dict_)
    for i in range(len(key_list)):
        reduced = reduced[key_list[i]]
    return reduced

# this solution isn't mine, see the link below
def set_val(dict_, key_list, value_):
    for key in key_list[:-1]:
        dict_ = dict_.setdefault(key, {})
    dict_[key_list[-1]] = value_
  • get_val() Where the key_list is the result of string.slit('.') and dict_ is the x dictionary in your case. You can leave out the copy.deepcopy() part, that's just for paranoid peeps like me - the reason is the python dict is not immutable, thus working on a deepcopy (a separate but exact copy in the memory) is a solution.
  • set_val() As I said it's not my idea, credit to @Bakuriu
    dict.setdefault(key, default_value) will take care of non-existing keys in x.

(3) evaluating a string as code with eval() and/or exec()

So here's an ugly unsafe solution:

def chainer(key_list):
    new_str = ''
    for key in key_list:
        new_str = "{}['{}']".format(new_str, key)
    return new_str

x = {'request': {'context': {'user_id': 'is this what you are looking for?'}}}
keys = 'request.context.user_id'.split('.')
chained_keys = chainer(keys)

# quite dirty but you may use eval() to evaluate a string
print( eval("x{}".format(chained_keys)) )

# will print
is this what you are looking for?

which is the innermost value of the mockup x dict

I assume you could use this in your code like this

data = [x for x in logData if eval("x{}".format(chained_keys)) in listX]
# or in python 3.x with f-string
data = [x for x in logData if eval(f"x{chained_keys}") in listX]

...or something similar.

Similarly, you can use exec() to execute a string as code if you wanted to write to x, though it's just as dirty and unsafe.

exec("x{} = '...or this, maybe?'".format(chained_keys))
print(x)

# will print
{'request': {'context': {'user_id': '...or this, maybe?'}}}

(2) An actual solution could be a recursive function as so:

def nester(key_list):
    if len(key_list) == 0:
        return 'value'   # can change this to whatever you like
    else:
        return {key_list.pop(0): nester(key_list)}

keys = 'request.context.user_id'.split('.')  
# ['request', 'context', 'user_id']

data = nester(keys)
print(data)

# will result
{'request': {'context': {'user_id': 'value'}}}

(1) A solution with list comprehension for split the string by '.' and use each element in the list as a dictionary key

data = {}
parts = 'request.context.user_id'.split('.')

if parts:   # one or more items
    [data.update({part: 'value'}) for part in parts]

print(data)

# the result
{'request': 'value', 'context': 'value', 'user_id': 'value'}

You can overwrite the values in data afterwards.

Gergely M
  • 583
  • 4
  • 11
0

If you want arbitrary counts, that means you need a loop. You can use get repeatedly to drill through layers of dictionaries.

parts = "request.context.user_id".split(".")
logData = [{"request": {"context": {"user_id": "jim"}}}]
listX = "jim"

def generate(logData, parts):
    for x in logData:
        ref = x
        # ref will be, successively, x, then the 'request' dictionary, then the
        # 'context' dictionary, then the 'user_id' value 'jim'. 
        for key in parts:
            ref = ref[key]
        if ref in listX:
            yield x


data = list(generate(logData, parts)))  # ['jim']
jnnnnn
  • 3,889
  • 32
  • 37