I have a string which I want to convert to a nested dictionary in Python.
Example Input :
import copy
diff_str = "/pathConstraint/latency/latencyValue"
value = "low"
diff_arr = diff.split("/")
final_temp_dict = dict()
for elem in reversed(diff_arr):
if len(final_temp_dict) == 0:
final_temp_dict.setdefault(elem, value)
else:
temp_final_dict = copy.deepcopy(final_temp_dict)
final_temp_dict.setdefault(elem, temp_final_dict)
print (final_temp_dict)
While running this I face an error and I'm not getting the expected output.
The output needed is as a nested dictionary:
{"pathConstraint" : {"latency" : {"latencyValue" : "low"}}}
You could use the following recursive function:
def string_to_dict(keys, value):
key = keys.split('/')
if len(key) == 2:
return {key[1]: value}
else:
return string_to_dict('/'.join(key[:-1]), {key[-1]: value})
Output:
>>> string_to_dict(diff_str, value)
{'pathConstraint': {'latency': {'latencyValue': 'low'}}}
Note that this assumes that diff_str begins with a / character.
The following is an iterative approach. Note diff_arr[1:] is used to exclude the empty string that is generated from splitting on the initial /.
diff_str = "/pathConstraint/latency/latencyValue"
value = "low"
diff_arr = diff_str.split("/")
for key in list(reversed(diff_arr[1:])):
value = {key: value}
print(value)
Output
{'pathConstraint': {'latency': {'latencyValue': 'low'}}}
Shorter recursive approach:
def to_dict(d, v):
return v if not d else {d[0]:to_dict(d[1:], v)}
diff_str = "/pathConstraint/latency/latencyValue"
value = "low"
print(to_dict(list(filter(None, diff_str.split('/'))), value))
Output:
{'pathConstraint': {'latency': {'latencyValue': 'low'}}}
I tried to modify your function as little as possible, this should work just fine
import copy
def func():
diff_str = "/pathConstraint/latency/latencyValue"
value = "low"
diff_arr = diff_str.split("/")
final_temp_dict = dict()
for elem in reversed(diff_arr):
if elem == "":
continue
if len(final_temp_dict) == 0:
final_temp_dict[elem] = value
else:
temp_final_dict = copy.deepcopy(final_temp_dict)
final_temp_dict = {}
final_temp_dict[elem] = temp_final_dict
print (final_temp_dict)
However, there are much nicer ways to do something like this. See the other answers for inspiration.
def convert(items, value):
if not items:
return value
return {items.pop(0): convert(items, value)}
print(convert(diff_str.strip('/').split('/'), 'low'))
Related
If i know there will be three levels of json file, and I wanna match the third level of text. i can have code shown like below. But if i don't know exact number of levels, how can I write a generic function for this in python? Any help would be appreciated.
Thanks
values=['text1','text2','text3']
event_json={'text1':[{
'othertext': {},
'text2': [{
'text3':{.....},
'othertext1': {},
....}],
...}]}
def function():
if event_json:
for event in event_json['text1']:
for activity in event['text2']:
if 'text3' in activity and
activity['text3'] == expected_name:
print('Match the text')
You may iterate over the values of the path you have then depending the current value if a dict or list you check into
values = ['text1', 'text2', 'text3']
event_json = {'text1': [{'text2': [{'text3': "LOOKFOR"}], }]}
def function(expected):
value = event_json
for key in values:
if isinstance(value, dict):
if key not in value:
print(value, key)
return -1
value = value[key]
elif isinstance(value, list):
for obj in value:
if key in obj:
value = obj[key]
break
else:
return -2
return value == expected # if it's array change to : expected in value
if __name__ == '__main__':
print(function("LOOKFOR")) # true
print(function("LOOK-FOR")) # false
I have a code to get a specific value from Yahoo API. The problem is it matched the IF statement but it returns None and also again its going to the else loop for some reason. I am very new to python.
I want to get the value of key astronomy as return.
import requests
def walk(d = None,val = None):
if val == 'astronomy':
return (val,d)
else:
for k,v in d.items():
if isinstance(v,dict):
p = d[k]
walk(d=p,val=k)
r = requests.get('https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22nome%2C%20ak%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys',stream = True)
n = r.json()
b = walk(d=n)
print(b)
There is no need to pick up the keys or to have a recursive call just to pull out the value - as long as your data is nested in dictionaries within dictionaries all you need is to recursively iterate through their values until you find a one containing your key:
import requests
def find_value(data, key):
if key in data:
return data[key]
for v in data.values():
if isinstance(v, dict):
v = find_value(v, key)
if v is not None:
return v
r = requests.get(
'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast'
'%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D'
'%22nome%2C%20ak%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys',
stream=True)
n = r.json()
b = find_value(n, "astronomy")
print(b) # {'sunset': '3:57 pm', 'sunrise': '11:58 am'}
You are recursing, so when it finds astronomy it returns only from that call back into the loop. You need to test the return value from your function and use that. For example:
import requests
def walk(d = None,val = None):
print("val:", val)
if val == 'astronomy':
return (val,d)
else:
for k,v in d.items():
if isinstance(v,dict):
p = d[k]
rtn = walk(d=p,val=k) # Changed
if not rtn is None: # Added
return rtn # Added
r = requests.get('https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22nome%2C%20ak%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys',stream = True)
n = r.json()
b = walk(d=n)
print(b)
The following Code produces an error, if there is only one "car" in "garage":
import xmltodict
mydict = xmltodict.parse(xmlstringResults)
for carsInGarage in mydict['garage']['car']:
# do something...
The Reason is that mydict['garage']['car'] is only a list if there is more than one element of "car". So I did something like this:
import xmltodict
mydict = xmltodict.parse(xmlstringResults)
if isinstance(mydict['garage']['car'], list):
for carsInGarage in mydict['garage']['car']:
# do something for each car...
else:
# do something for the car
to get the code to run. But for more advanced operations this is no solution.
Does someone know some kind of function to use, even if there is only one element?
This problem is discussed in this issue on Github. The xmltodict package now supports
d = xmltodict.parse(s, force_list={'car'})
Although this still doesn't create an empty list if the field is absent.
This is of course not an elegant way, but this is what i have done to get the code run (if someone hase the same probleme an found this via google):
import xmltodict
def guaranteed_list(x):
if not x:
return []
elif isinstance(x, list):
return x
else:
return [x]
mydict = xmltodict.parse(xmlstringResults)
for carsInGarage in guaranteed_list(mydict['garage']['car']):
# do something...
but i thing i will write my code again and "use XML directly" as one of the comments said.
I am using the combination of
1)
json_dict = xmltodict.parse(s, force_list={'item'})
And
2)
# Removes a level in python dict if it has only one specific key
#
# Examples:
# recursive_skip_dict_key_level({"c": {"a": "b"}}, "c") # -> {"a", "b"}
# recursive_skip_dict_key_level({"c": ["a", "b"]}, "c") # -> ["a", "b"]
#
def recursive_skip_dict_key_level(d, skipped_key):
if issubclass(type(d), dict):
if list(d.keys()) == [skipped_key]:
return recursive_skip_dict_key_level(d[skipped_key], skipped_key)
else:
for key in d.keys():
d[key] = recursive_skip_dict_key_level(d[key], skipped_key)
return d
elif issubclass(type(d), list):
new_list = []
for e in d:
new_list.append(recursive_skip_dict_key_level(e, skipped_key))
return new_list
else:
return d
# Removes None values from a dict
#
# Examples:
# recursive_remove_none({"a": None}) # -> {}
# recursive_remove_none([None]) # -> []
#
def recursive_remove_none(d):
if issubclass(type(d), dict):
new_dict = {}
for key in d.keys():
if not (d[key] is None):
new_dict[key] = recursive_remove_none(d[key])
return new_dict
elif issubclass(type(d), list):
new_list = []
for e in d:
if not (e is None):
new_list.append(recursive_remove_none(e))
return new_list
else:
return d
json_dict = recursive_skip_dict_key_level(json_dict, "item")
json_dict = recursive_remove_none(json_dict)
to interpret any "item" XML-elements as lists.
In addition to the existing answers, xmltodict now also supports the following to force everything to be a list:
xml = xmltodict.parse(s, force_list=True)
Given the following dictionary:
d = {"a":{"b":{"c":"winning!"}}}
I have this string (from an external source, and I can't change this metaphor).
k = "a.b.c"
I need to determine if the dictionary has the key 'c', so I can add it if it doesn't.
This works swimmingly for retrieving a dot notation value:
reduce(dict.get, key.split("."), d)
but I can't figure out how to 'reduce' a has_key check or anything like that.
My ultimate problem is this: given "a.b.c.d.e", I need to create all the elements necessary in the dictionary, but not stomp them if they already exist.
You could use an infinite, nested defaultdict:
>>> from collections import defaultdict
>>> infinitedict = lambda: defaultdict(infinitedict)
>>> d = infinitedict()
>>> d['key1']['key2']['key3']['key4']['key5'] = 'test'
>>> d['key1']['key2']['key3']['key4']['key5']
'test'
Given your dotted string, here's what you can do:
>>> import operator
>>> keys = "a.b.c".split(".")
>>> lastplace = reduce(operator.getitem, keys[:-1], d)
>>> lastplace.has_key(keys[-1])
False
You can set a value:
>>> lastplace[keys[-1]] = "something"
>>> reduce(operator.getitem, keys, d)
'something'
>>> d['a']['b']['c']
'something'
... or using recursion:
def put(d, keys, item):
if "." in keys:
key, rest = keys.split(".", 1)
if key not in d:
d[key] = {}
put(d[key], rest, item)
else:
d[keys] = item
def get(d, keys):
if "." in keys:
key, rest = keys.split(".", 1)
return get(d[key], rest)
else:
return d[keys]
How about an iterative approach?
def create_keys(d, keys):
for k in keys.split("."):
if not k in d: d[k] = {} #if the key isn't there yet add it to d
d = d[k] #go one level down and repeat
If you need the last key value to map to anything else than a dictionary you could pass the value as an additional argument and set this after the loop:
def create_keys(d, keys, value):
keys = keys.split(".")
for k in keys[:-1]:
if not k in d: d[k] = {}
d = d[k]
d[keys[-1]] = value
I thought this discussion was very useful, but for my purpose to only get a value (not setting it), I ran into issues when a key was not present. So, just to add my flair to the options, you can use reduce in combination of an adjusted dict.get() to accommodate the scenario that the key is not present, and then return None:
from functools import reduce
import re
from typing import Any, Optional
def find_key(dot_notation_path: str, payload: dict) -> Any:
"""Try to get a deep value from a dict based on a dot-notation"""
def get_despite_none(payload: Optional[dict], key: str) -> Any:
"""Try to get value from dict, even if dict is None"""
if not payload or not isinstance(payload, (dict, list)):
return None
# can also access lists if needed, e.g., if key is '[1]'
if (num_key := re.match(r"^\[(\d+)\]$", key)) is not None:
try:
return payload[int(num_key.group(1))]
except IndexError:
return None
else:
return payload.get(key, None)
found = reduce(get_despite_none, dot_notation_path.split("."), payload)
# compare to None, as the key could exist and be empty
if found is None:
raise KeyError()
return found
In my use case, I need to find a key within an HTTP request payload, which can often include lists as well. The following examples work:
payload = {
"haystack1": {
"haystack2": {
"haystack3": None,
"haystack4": "needle"
}
},
"haystack5": [
{"haystack6": None},
{"haystack7": "needle"}
],
"haystack8": {},
}
find_key("haystack1.haystack2.haystack4", payload)
# "needle"
find_key("haystack5.[1].haystack7", payload)
# "needle"
find_key("[0].haystack5.[1].haystack7", [payload, None])
# "needle"
find_key("haystack8", payload)
# {}
find_key("haystack1.haystack2.haystack4.haystack99", payload)
# KeyError
EDIT: added list accessor
d = {"a":{}}
k = "a.b.c".split(".")
def f(d, i):
if i >= len(k):
return "winning!"
c = k[i]
d[c] = f(d.get(c, {}), i + 1)
return d
print f(d, 0)
"{'a': {'b': {'c': 'winning!'}}}"
i have a problem with my python class. it contains a method that goes through all the keys of a multi_dimensional dictionary. The dictionary keys may be in the following order (1->(2,3),2->(5,6)). the problem is when the method attempts to get the keys, sometimes it gets them in the right order (1,2) and sometimes it gets them in the wrong order (2,1). any help will be appreciated. below is a very simple example of what the code might look like
class tree:
tree_as_string = ""
def __init__(self):
self.id = ""
self.daughters = {1 = 'node0', 2 = 'node1'}
def get_as_string(self):
s = ''
for key in self.daughters:
tree_as_string = s.join([tree_as_string, key])
return tree_as_string
Note that dictionaries are unordered so in order to be sure that values would be handled in the ordered format you need to sort them first. Please find sample below:
d={1:{2:'tst', 3:'tst2'}, 4:{...} }
for key in sorted(d):
for skey in sorted(d[key]):
#do something
OR something like this:
from operator import itemgetter
d={1:{2:'tst', 3:'tst2'}, 4:{6:'tst7', 7:'tst12'} }
for key, val in sorted(d.items(), key=itemgetter(0)):
for skey, sval in sorted(val.items(), key=itemgetter(0)):
print key, skey, sval
This means that in your case:
class tree(object):
tree_as_string = ""
def __init__(self):
self.id = ""
self.daughters = {1 = 'node0', 2 = 'node1'}
def get_as_string(self):
s = ''
for key in sorted(self.daughters):
tree_as_string = s.join([tree_as_string, key])
return tree_as_string
You can use sorted (which I would suggest because it reduces your code even further example is below), or just call sort on keys. Sort doesn't return a value, it just sorts whatever list is provided.
class tree:
def __init__(self):
self.id = ""
self.daughters = {10: "test10", 2 : 'node2', 1 :'node1', 0 : 'node0'}
def get_as_string_using_sorted(self):
''' Makes me happy'''
return '->'.join(str(k) for k in sorted(self.daughters))
def get_as_string(self):
s = '->'
keys = self.daughters.keys()
keys.sort()
return s.join(str(k) for k in keys)
t = tree()
print t.get_as_string()
print t.get_as_string_using_sorted()
Side note I changed your code a bit.
I fixed your dict syntax its k:v verus k=v
I initialized tree_as_string ="" you defined a class variable but never used it.
I added str(key) because key is an int.
Added more test numbers
changed s to ->
Simplified your for loop.