Remove keys from a nested dict (Python keys) - python

I'm pretty new in Python, thanks in advance for your help.
I built the following code (I tried the below, I used a dictionary within a dictionary).
The idea is to keep the keys (hair.color) with values(blonde). In this example: remove Micheal.
Code:
def answers(hair_questions):
try:
for i in people:
if people[i]["hair.color"]==hair_questions:
print(people[i])
else:
del people[i]
return people[i]
except:
print("Doesn´t exist")
answers("brown")
On People:
people={
"Anne":
{
"gender":"female",
"skin.color":"white",
"hair.color":"blonde",
"hair.shape":"curly"
}
,
"Michael":
{
"citizenship":"africa",
"gender":"male",
"hair.color":"brown",
"hair.shape":"curly"
}
,
"Ashley":
{
"gender":"female",
"citizenship":"american",
"hair.color":"blonde",
"hair.shape":"curly "
}
}
The code only check the first key: under the condition: values(blonde) i.e. (people[i]["hair.color"]!=brown) it works just for 1 key and then the code gets "stuck"
My current output:
"people"=
"Michael":
{
"citizenship":"africa",
"gender":"male",
"hair.color":"brown",
"hair.shape":"curly"
}
,
"Ashley":
{
"gender":"female",
"citizenship":"american",
"hair.color":"blonde",
"hair.shape":"curly "
}
Instead, I wanted:
"people"=
"Michael":
{
"citizenship":"africa",
"gender":"male",
"hair.color":"brown",
"hair.shape":"curly"
}
I want an output, for this case, (only) Michael.

You can't delete key while iterating for loop:
people={
"Anne":
{
"gender":"female",
"skin.color":"white",
"hair.color":"blonde",
"hair.shape":"curly"
},
"Michael":
{
"citizenship":"africa",
"gender":"male",
"hair.color":"brown",
"hair.shape":"curly"
},
"Ashley":
{
"gender":"female",
"citizenship":"american",
"hair.color":"blonde",
"hair.shape":"curly "
}
}
def answers(hair_questions):
my_dict = {}
for i in people:
if people[i]["hair.color"] in hair_questions:
my_dict[i] = people[i]
return my_dict
print(answers("brown"))
OR
def answers(hair_questions):
my_list = []
for i in people:
if people[i]["hair.color"] not in hair_questions:
my_list.append(i)
for i in my_list:
del people[i]
answers("brown")
print(people)
O/P:
{'Michael': {'citizenship': 'africa', 'gender': 'male', 'hair.color': 'brown', 'hair.shape': 'curly'}}

you can use list comprehension:
brown = {key:value for key,value in people.items() if people[key]["hair.color"] != "blonde"}
print (brown)
what is equal to:
brown= {}
for key,value in people.items():
if people[key]["hair.color"] != "blonde":
brown[key] = value
print (brown)
output:
{'Michael': {'citizenship': 'africa', 'gender': 'male', 'hair.color': 'brown', 'hair.shape': 'curly'}}

Related

unable to update JSON using python

I am trying to update transaction ID from the following json:
{
"locationId": "5115",
"transactions": [
{
"transactionId": "1603804404-5650",
"source": "WEB"
} ]
I have done following code for the same, but it does not update the transaction id, but it inserts the transaction id to the end of block:-
try:
session = requests.Session()
with open(
"sales.json",
"r") as read_file:
payload = json.load(read_file)
payload["transactionId"] = random.randint(0, 5)
with open(
"sales.json",
"w") as read_file:
json.dump(payload, read_file)
Output:-
{
"locationId": "5115",
"transactions": [
{
"transactionId": "1603804404-5650",
"source": "WEB"
} ]
}
'transactionId': 1
}
Expected Outut:-
{
"locationId": "5115",
"transactions": [
{
"transactionId": "1",
"source": "WEB"
} ]
This would do it, but only in your specific case:
payload["transactions"][0]["transactionId"] = xxx
There should be error handling for cases like "transactions" key is not int the dict, or there are no records or there are more than one
also, you will need to assign =str(your_random_number) not the int if you wish to have the record of type string as the desired output suggests
If you just want to find the transactionId key and you don't know exactly where it may exist. You can do-
from collections.abc import Mapping
def update_key(key, new_value, jsondict):
new_dict = {}
for k, v in jsondict.items():
if isinstance(v, Mapping):
# Recursive traverse if value is a dict
new_dict[k] = update_key(key, new_value, v)
elif isinstance(v, list):
# Traverse through all values of list
# Recursively traverse if an element is a dict
new_dict[k] = [update_key(key, new_value, innerv) if isinstance(innerv, Mapping) else innerv for innerv in v]
elif k == key:
# This is the key to replace with new value
new_dict[k] = new_value
else:
# Just a regular value, assign to new dict
new_dict[k] = v
return new_dict
Given a dict-
{
"locationId": "5115",
"transactions": [
{
"transactionId": "1603804404-5650",
"source": "WEB"
} ]
}
You can do-
>>> update_key('transactionId', 5, d)
{'locationId': '5115', 'transactions': [{'transactionId': 5, 'source': 'WEB'}]}
Yes because transactionId is inside transactions node. So your code should be like:
payload["transactions"][0].transactionId = random.randint(0, 5)
or
payload["transactions"][0]["transactionId"] = random.randint(0, 5)

Get key value from nested dict python

I have class with a nested dictionary data object. I need to get all the key values from it. What's the best efficient way to do this?
I'm stuck with following:
for k,v in data.items():
print v.keys()
This is the data:
data = {
"BANK": {
"no_data": "INT",
},
"SHOCK": {
"drop": "NOTI",
"rise": "NOTI",
"high_risk": "ALERT",
},
"OFFLINE": {"online": None, "offline_few": "ALERT"},
}
An elegant way to concatenate lists (your value.keys() lists) into one is using a double-loop list comprehenstion, like this:
nested_keys = [
key
for val in data.values()
for key in val.keys()]
Using a generator:
def all_keys(d):
for k, v in d.items():
yield k
# assume that anything with `items` property will be a mapping
# this can be replaced with: `isinstance(v, dict)` or `isinstance(v, collections.Mapping)`
if hasattr(v, 'items'):
yield from all_keys(v)
On your input this produces:
data = {
"BANK": {
"no_data": "INT",
},
"SHOCK": {
"drop": "NOTI",
"rise": "NOTI",
"high_risk": "ALERT",
},
"OFFLINE": {"online": None, "offline_few": "ALERT"},
}
print(list(all_keys(data)))
# ['BANK', 'no_data', 'SHOCK', 'drop', 'rise', 'high_risk', 'OFFLINE', 'online', 'offline_few']
If all your "actual" key-value pairs are at a certain depth, for example for depth 1, you can go like:
data = {
"BANK": {
"no_data": "INT",
},
"SHOCK": {
"drop": "NOTI",
"rise": "NOTI",
"high_risk": "ALERT",
},
"OFFLINE": {"online": None, "offline_few": "ALERT"},
}
dic = {k:v for val in data.values() for k,v in val.items()}
But if you dont know that:
data = {
"BANK": {
"no_data": "INT",
},
"SHOCK": {
"drop": "NOTI",
"rise": "NOTI",
"high_risk": "ALERT",
},
"online": None,
"offline_few": "ALERT"
}
In this case you need to use recursion:
def unnest(dic, final=dict()):
for key, val in dic.items():
if not isinstance(val, dict):
final[key] = val
else:
dic2 = dict()
for k, v in val.items():
dic2[k] = v
unnest(dic2, final)
return final
dic = unnest(data, {}) #every use of the function should have {} to solve issue pointed by #Ch3steR
In any case, once you have the "un-nested" dictionary it is trivial to print out the keys:
print(dic.keys())
Recursive Function
Gets all keys at all levels of nested dictionary
def get_keys(d, result = None):
# use default of None to fix issue noted by #Ch3steR
# namely: http://effbot.org/zone/default-values.htm
if result is None:
result = []
for k, v in d.items():
if isinstance(v, dict):
result.append(k)
get_keys(v, result)
else:
result.append(k)
return result
Test
print(get_keys(data))
Output
['BANK', 'no_data', 'SHOCK', 'drop', 'rise', 'high_risk', 'OFFLINE', 'online', 'offline_few']
You could use a NestedDict. First install ndicts
pip install ndicts
Then
from ndicts.ndicts import NestedDict
data = {
"BANK": {
"no_data": "INT",
},
"SHOCK": {
"drop": "NOTI",
"rise": "NOTI",
"high_risk": "ALERT",
},
"OFFLINE": {"online": None, "offline_few": "ALERT"},
}
nd = NestedDict(data)
The result
>>> list(nd.keys())
[('BANK', 'no_data'), ('SHOCK', 'drop'), ('SHOCK', 'rise'), ('SHOCK', 'high_risk'), ('OFFLINE', 'online'), ('OFFLINE', 'offline_few')]

Update key without affecting the key's values within a nested dictionary

I am trying to update a key while retaining its values within a nested dictionaries.
While I have found a method to do so, I had to create new dictionaries in order to cater for it. As such, wondering if there anyone could provide me with a better insight on the approach I have taken?
init_dict = {
'pageA' : {
0 : {
'menuA' : [
'a01',
'a02'
]
}
},
'pageB' : {
1 : {
'menuB' : [
'b10'
]
}
}
}
changed = {'pageB' : 0, 'pageA' : 1}
condense_dict = {}
for k, v in init_dict.items():
for i in v.keys():
condense_dict[k] = init_dict[k][i]
new_dict = {}
for i in condense_dict:
new_dict[i] = {}
new_dict[i][changed.get(i)] = condense_dict.get(i)
My expected output is as follows:
{
'pageA' : {
1 : {
'menuA' : [
'a01',
'a02'
]
}
},
'pageB' : {
0 : {
'menuB' : [
'b10'
]
}
}
}
You can pop the presumably only key from the sub-dict and assign it to the new key for each entry in changed:
for k, v in changed.items():
init_dict[k][v] = init_dict[k].pop(next(iter(init_dict[k])))
init_dict becomes:
{'pageA': {1: {'menuA': ['a01', 'a02']}}, 'pageB': {0: {'menuB': ['b10']}}}
Using the .pop() method this can be done similar to this (although I'm sure you could rewrite it better)
init_dict = {
'pageA': {
0: {
'menuA' : [
'a01',
'a02'
]
}
},
'pageB': {
1: {
'menuB': [
'b10'
]
}
}
}
print(init_dict)
thing = init_dict.pop('pageA')
sub_thing = thing.pop(0)
redone = {1: sub_thing}
init_dict.update({'pageA': redone})
print(init_dict)
{'pageA': {0: {'menuA': ['a01', 'a02']}}, 'pageB': {1: {'menuB': ['b10']}}}
{'pageA': {1: {'menuA': ['a01', 'a02']}}, 'pageB': {1: {'menuB': ['b10']}}}
You can see it's the same data as we start with, but we changed 0 to 1
Here I use .pop() and change it inplace. With the same init_dict as you:
change_to = {1: 0, 0: 1}
for k, v in init_dict.items():
for old_key in v.keys():
if old_key in change_to:
v[change_to[old_key]] = v.pop(old_key)

Why isn't this nested dict comprehension working in Python?

nested_dict = { b: { a: some_other_source_dict[b][a] or {} for a in a_list } for b in b_list }
If some_other_source_dict[b][a] exists, the correct output should be:
nested_dict = { b_key_1: { a_key_1: a_val_1, a_key_2: a_val_2 },
b_key_2: { a_key_1: a_val_3, a_key_2: a_val_4 } }
If it doesn't exist, the output should be:
nested_dict = { b_key_1: { a_key_1: {}, a_key_2: {} },
b_key_2: { a_key_1: {}, a_key_2: {} } }
some_other_source_dict[b][a] doesn't return a falsy value if it doesn't exist, it just errors. You want something like { a: some_other_source_dict[b][a] for a in a_list } if "some_other_source_dict" in globals() else {}. Preferably, you should have some way of determining whether or not it's defined without needing to check globals().

aggregating values in dictionary

I have a deep dictionary like this:
myDict = { '123456': {
'348adbd39r' : {
'LONDON': {
'c_name': 'abc',
'acct': '84720'
},
'PARIS': {
'c_name': 'xyz',
'acct': '73642'
}
},
'2862aef3' : {
'NYC': {
'c_name': 'hhdls3',
'acct': '92742'
}
},
'82gfg24' : {
'NYC': {
'c_name': 'hquer',
'acct': '34567'
},
'PARIS': {
'c_name': 'ljad',
'acct': '93742'
}
}
}
I want to 'aggregate' it based on the city names. The output should look like below:
outDict = {
'LONDON': {
'c_name': ['abc'],
'acct': ['84720']
},
'PARIS': {
'c_name': ['xyz', 'ljad'],
'acct': ['73642', '93742']
},
'NYC': {
'c_name': ['hhdls3', 'hquer'],
'acct': ['73642', '34567']
}
}
This is what I did:
cust_fields = ['c_name', 'acct']
field_dict = {field: [] for field in cust_fields}
aggregated_dict = {}
city_names = ['LONDON', 'PARIS', 'NYC']
for city in city_names:
aggregated_dict[city] = field_dict
for id, an_dict in myDict.iteritems():
for alphaNum, city_dict in an_dict.iteritems():
for city, acct_dict in city_dict.iteritems():
for field, val in acct_dict.iteritems():
aggregated_dict[city][field].append(val)
But, the above is updating the field-values for all the cities...rather than just the particular city it is working on. Not sure where the logic is wrong. Any help is appreciated (either correct where my mistake is or any new logic...).
Thanks!
The problem you have is you are assigning field_dict to the value of aggregated_dict[city] in your loop over city_names, which is simply assigning the same dictionary to each city. And when you update any reference (for any city) all references are updated.
An easy fix for this is to change
for city in city_names:
aggregated_dict[city] = field_dict
To:
for city in city_names:
aggregated_dict[city] = {field: [] for field in cust_fields}
I would also look at collections.defaultdict for this type of aggregation.
from collections import defaultdict
collected = defaultdict(lambda: defaultdict(list))
for _, city_records in myDict['123456'].items():
for city_name, records in city_records.items():
for record_name, record_value in records.items():
collected[city_name][record_name].append(record_value)
for city_name, records in collected.items():
print city_name
print dict(records)
for key,val in myDict.items():
for key1,val1 in val.items():
for key2,val2 in val1.items():
d = final_dict[key2] if key2 in final_dict else defaultdict(list)
for k,v in val2.items():
d[k].append(v)
final_dict[key2] = d
You can use recursion:
from collections import defaultdict
d1 = defaultdict(dict)
def aggregate(d):
for a, b in d.items():
if a in ['LONDON', 'PARIS', 'NYC']:
global d1
if a not in d1:
d1[a] = {}
d1[a]['c_name'] = [b['c_name']]
d1[a]['acct'] = [b['acct']]
else:
d1[a]['c_name'].append([b['c_name']])
d1[a]['acct'].append(b['acct'])
else:
aggregate(b)
aggregate(myDict)
print(dict(d1))
Output:
{'PARIS': {'acct': ['73642', '93742'], 'c_name': ['xyz', ['ljad']]}, 'NYC': {'acct': ['92742', '34567'], 'c_name': ['hhdls3', ['hquer']]}, 'LONDON': {'acct': ['84720'], 'c_name': ['abc']}}

Categories

Resources