Store the route to the "#myID" based on the key value - python

I have a json in the following format.
json_tree ={
"Garden": {
"Seaside": {
"#loc": "127.0.0.1",
"#myID": "1.3.1",
"Shoreside": {
"#myID": "3",
"InfoList": {
"Notes": {
"#code": "0",
"#myID": "1"
},
"Count": {
"#myID": "2",
"#val": "0"
}
},
"state": "0",
"Tid": "3",
"Lakesshore": {
"#myID": "4",
"InfoList": {
"Notes": {
"#code": "0",
"#oid": "1"
},
"Count": {
"#myID": "2",
"#val": "0"
}
},
"state": "0",
"Tid": "4"
}
},
"state": "0",
"Tid": "2"
},
"Tid": "1",
"state": "0"
}
}
My implementation:
def getmyID(json_tree, itemList):
for k1, v1 in json_tree .items():
for k,v in v1.items():
if k == itemList:
return '{}{}.{}'.format(json_tree['#myID'] + '.' if '#myID' in json_tree else '',
v['#myID'], v['InfoList']['status']['#myID'])
The issue I have is that this method does not work when as it returns "None" when I want to find the route to "BackYard". Note the 'BackYard" is nested inside the "Seaside" node.
I am appending the "#myID" of the key node to the "#myID" in the respective "status" node.
getmyID(json_tree, "Seaside")
"Output" = "1.2.3.26" --> Currently I get this only
getmyID(json_tree, "BackYard")
"Output" = "1.2.3.75.32" --> expected output but getting "None"
The expected output of "Backyard" is created by concatenating the "#myID" of the root node which it is nested in, that is "Seaside" in this case, and concatenating it with the "#myID" of "backYard" and the "#myID" of its "status" node.
"For a nested node, output string is formed by":
["root #myID" + "nested node "#myID" + "status node #myID"] or ["1.2.3" + "75" + "23"] for "Backyard".
"For a level node, output string is formed by":
["root #myID" + "status node #myID"] or ["1.2.3" + "26"] for "Seaside".
Any help would be appreciated.
Thanks.

You can use recursion with a generator:
def get_vals(d, target, path = []):
for a, b in d.items():
if a == target:
yield '.'.join(filter(None, path+[b['#myID'], b["InfoList"]['status']['#myID']]))
if isinstance(b, dict):
yield from get_vals(b, target, path + [b.get('#myID', '')])
print(list(get_vals(json_tree, "Seaside")))
print(list(get_vals(json_tree, "BackYard")))
Output:
['1.2.3.26']
['1.2.3.75.32']
Edit: newest data:
def get_vals(d, target, paths = []):
for a, b in d.items():
if a == target:
yield '.'.join(filter(None, paths+[b["#myID"], b.get("InfoList", {}).get('Notes', {}).get('#myID')]))
if isinstance(b, dict):
yield from get_vals(b, target, paths+[b.get('#myID')])
print(list(get_vals(json_tree, "Seaside")))
print(list(get_vals(json_tree, 'Shoreside')))
Output:
['1.3.1']
['1.3.1.3.1']

EDIT - I have no problem with people down-voting my answers or even editing them, as long as they tell me why. Would love to hear back from Yvette Colomb and ChrisF.
Here is the solution I came up with using a stack-based approach. The idea is that you try to go as deep as you can in your nested structure before finding the desired key (what you called itemList). I'm sure there must be a cuter way of doing this:
json_tree = {
"Gardens": {
"Seaside": {
"#loc": "porch",
"#myID": "1.2.3",
"Tid": "1",
"InfoList": {
"status": {
"#default": "0",
"#myID": "26"
},
"count": {
"#default": "0",
"#myID": "1"
}
},
"BackYard": {
"#myID": "75",
"Tid": "2",
"InfoList": {
"status": {
"#default": "6",
"#myID": "32"
},
"count": {
"#default": "0",
"#myID": "2"
}
}
}
}
}
}
def is_valid_kv_pair(key, value):
try:
assert isinstance(key, str)
assert isinstance(value, dict)
assert isinstance(value["#myID"], str)
assert isinstance(value["Tid"], str)
assert isinstance(value["InfoList"], dict)
assert isinstance(value["InfoList"]["status"], dict)
assert isinstance(value["InfoList"]["status"]["#default"], str)
assert isinstance(value["InfoList"]["status"]["#myID"], str)
except (AssertionError, KeyError):
return False
else:
return True
def get_id(dictionary, desired_key):
if not isinstance(dictionary, dict):
return None
dictionary_stack = [dictionary]
root_stack = []
while dictionary_stack:
current_dictionary = dictionary_stack.pop()
appended_root = False
for key, value in current_dictionary.items():
if appended_root:
root_stack.pop()
if is_valid_kv_pair(key, value):
if key == desired_key:
rootIDs = [d["#myID"] for d in root_stack]
myID = value["#myID"]
statusID = value["InfoList"]["status"]["#myID"]
return ".".join(rootIDs + [myID] + [statusID])
root_stack.append(value)
appended_root = True
if isinstance(value, dict):
dictionary_stack.append(value)
return None
ID = get_id(json_tree, "BackYard")
print(ID)

Related

How to change json key name with python

Need some help please.
I have a similar json file:
{
"timestamp": "2022-09-20T08:16:00.000Z",
"metadata": {
"orgID": "6780",
"projectId": 0988,
}
},
{
"data":
"workers": [
{
"identifiers": {
"FullName": null,
"NINumber": null,
"CompID": null
},
"lastName": null,
"costCenter": null
},
{
"codes": [
{
"source": {
"name": "net_salary",
"value": 11500
},
"name": "net_salary",
"code": "rt_sa",
"value": 11500
},
{
"identifiers": {
"FullName": null,
"NINumber": null,
Comp ID": null
},
"lastName": null,
"costCenter": null
},
{
"codes": [
{
"source": {
"name": "hiredate",
"value": 3.333
},
"name": "hiredate",
"code": "h_code",
"value": 3.333
},
I want to change the key names under source from name->fieldname and value to fieldvalue.
However, I don't want to change the keys where there are the keys: name, code, value.
I tried this but it is not correct:
with open(r'C:\Users\Administrator\Documents\test\PJSON.json') as f:
payrolldata = json.load(f)
source = payrolldata[1]['data']['workers'][1]['codes'][1]['source']
print(source)
oldvalue = source.keys()
print(str(oldvalue).replace('name', 'newname').replace('value', 'value2'))
payrolldata = str(oldvalue).replace('name', 'newname').replace('value', 'newvalue2')
for d in payrolldata:
d['newName':] = d.pop["'name':"]
with open(r'C:\Users\Administrator\Documents\test\PJSON.json', "w") as f:
json.dump(payrolldata, f, indent=4)
I suggest you don't convert your dict into string and use something like this on you dict read from json file (with json.load)
def deep_replace_key(
d,
old_key: str,
new_key: str,
branch_name: str = None,
replace: bool = False,
):
"""deep replace key in dict.
Only make replacement if the we are in the branch branch_name."""
if branch_name is None:
replace = True
if isinstance(d, dict):
d_copy = d.copy()
for key, value in d_copy.items():
if key == old_key and replace:
d[new_key] = d.pop(old_key)
else:
if branch_name and key == branch_name:
deep_replace_key(value, old_key, new_key, branch_name, True)
else:
deep_replace_key(value, old_key, new_key, branch_name, False)
elif isinstance(d, list):
for item in d:
deep_replace_key(item, old_key, new_key, branch_name, replace)
return d
Here is a working test for this code
import unittest
# test
class TestDeepReplaceKey(unittest.TestCase):
def test_deep_replace_key(self):
d = {
"codes": [
{
"source": {
"name": "hiredate",
"value": 3.333
},
"not_source": {
"name": "hiredate",
"value": 3.333
},
},
{
"source": {
"name": "hiredate",
"value": 3.333
},
"not_source": {
"name": "hiredate",
"value": 3.333
},
},
]
}
d = deep_replace_key(d, "name", "new_name", "source", )
self.assertEqual(d["codes"][0]["source"]["new_name"], "hiredate")
self.assertEqual(d["codes"][0]["not_source"]["name"], "hiredate")
d = deep_replace_key(d, "name", "new_name", )
self.assertEqual(d["codes"][0]["not_source"]["new_name"], "hiredate")
So you can see if I call deep_replace_key(d, "name", "new_name", "source", ) the change only happens in the source block.
If I omit mentioning "source" like this deep_replace_key(d, "name", "new_name", ) change happens everywhere.

How to print specific key with key and value

A = {
"Ajit": {
"Place": "Bharasar",
"Age": "20"
},
"Deepika": {
"Place": "Mankuva",
"Age": "19"
}
}
I want to print specific key Ajit with Ajit's key it self like:
{
'Ajit': {
'Place': 'Bharasar',
'Age':'20'
}
}
Do you mean:
A = {"Ajit":{"Place":"Bharasar","Age":"20"}, "Deepika":{"Place":"Mankuva","Age":"19"}}
key = "Ajit"
print(f"{{'{key}': {A[key]}}}")
And please remember to put four spaces before your code samples to get proper formatting
You just create a new Dict like the following code:
A = {
"Ajit": {
"Place": "Bharasar",
"Age": "20"
},
"Deepika": {
"Place": "Mankuva",
"Age": "19"
}
}
B = {
"Ajit": A["Ajit"]
}
print(B)
If you want Ajit value, you can get it with A["Ajit"]
However, if you want to have the key and its associated value, you can print
A = {
"Ajit": {
"Place": "Bharasar",
"Age": "20"
},
"Deepika": {
"Place": "Mankuva",
"Age": "19"
}
}
key = "Ajit"
print("{" + key + ":" + A[key] + "}")

Remove keys but keep values from dictionary

Let's say I have this dictionary:
{
"id": "132-sd-sa-23-a-1",
"data": {
"lastUpdated": { "S": "2020-07-22T21:39:20Z" },
"profile": {
"M": {
"address": { "L": [] },
"fakeField": { "S": "someValue" },
"someKey": { "M": { "firstName": { "S": "Test" } } }
}
},
"groups": {
"L": [{ "S": "hello world!" }]
}
}
}
How can I remove the "M", "S", "L", etc. keys from the dictionary but keep the values. So it would turn into this:
{
"id": "132-sd-sa-23-a-1",
"data": {
"lastUpdated": "2020-07-22T21:39:20Z",
"profile": {
"address": [],
"fakeField": "someValue",
"someKey": { "firstName": "Test" }
},
"groups": ["hello world!"]
}
}
I could turn the dictionary into a string, loop through it, and remove what's necessary but that doesn't seem efficient or fast. I can't save the original list as the output that I'm hoping for initially so I need to convert it myself.
Sounds like a job for recursion:
def unsml(obj):
if isinstance(obj, dict):
if len(obj) == 1:
(key, value), *_ = obj.items() # get the only key and value
if key in "SML":
return unsml(value)
return {
key: unsml(value) if isinstance(value, dict) else value
for key, value
in obj.items()
}
elif isinstance(obj, list):
return [unsml(value) for value in obj]
return obj
Then just do new_dict = unsml(old_dict).

Remove all occurences of a value from a nested dictionary

I have a nested dictionary as the following.
myDict= {
"id": 10,
"state": "MY LIST",
"Stars":
{
"BookA": {
"id": 10,
"state": "new book",
"Mystery": {
"AuthorA":
{
"id": "100",
"state": "thriller"
},
"AuthorB":
{
"id": "112",
"state": "horror"
}
},
"Thriller": {
"Store1":
{
"id": "300",
"state": "Old"
}
}
}
}
}
I want to return a dictionary which has all of the "state": "text" removed. So that means, I want to remove all the "state" fields and have an output as below.
I want it to be generic method as the dictionary could be nested on many levels.
myDict=
{
id: 10,
"Stars":
{
"BookA": {
"id": 10
"Mystery": {
"AuthorA":
{
"id": "100"
},
"AuthorB":
{
"id": "112"
}
},
"Thriller": {
"Store1":
{
"id": "300"
}
}
}
}
I tried the following but it doesnt seem to work. It only removes the "state": "MY LIST". May someone help me to resolve the issue?
def get(self):
removelist= ["state"]
new_dict = {}
for key, item in myDict.items():
if key not in removelist:
new_dict.update({key: item})
return new_dict
It doesnt remove all the "state" values.
You can use a DFS:
def remove_keys(d, keys):
if isinstance(d, dict):
return {k: remove_keys(v, keys) for k, v in d.items() if k not in keys}
else:
return d
The idea is to remove recursively the keys from subtrees: for every subtree that is a nested dict, return a dict without the keys to remove, using a dict comprehension; for every leaf (that is a single value), just return the value.
Test:
from pprint import pprint
pprint(remove_keys(myDict, ['state']))
Output:
{'Stars': {'BookA': {'Mystery': {'AuthorA': {'id': '100'},
'AuthorB': {'id': '112'}},
'Thriller': {'Store1': {'id': '300'}},
'id': 10}},
'id': 10}
The problem is you aren't handling the nested dictionaries.
def get(self):
removelist= ["state"]
new_dict = {}
for key, item in myDict.items():
if key not in removelist:
new_dict.update({key: item})
if isinstance(item, dict):
# You'll need to handle this use case.
return new_dict
To elaborate, lets look back at your dictionary:
myDict= {
"id": 10, # int
"state": "MY LIST", # string
"Stars": { # dictionary
"BookA": {
"id": 10, # int
"state": "new book", # string
"Mystery": { # dictionary
"AuthorA": {
"id": "100",
"state": "thriller"
},
"AuthorB": {
"id": "112",
"state": "horror"
}
},
"Thriller": {
"Store1": {
"id": "300",
"state": "Old"
}
}
}
}
}
I commented in the types for clarity. Your code is currently parsing myDict and ignoring the key "state". Once you hit the value "Stars", you need to parse that dictionary to also ignore the key "state".

Python - create dynamic nested dictionary from list of dictionary

Below is sample list data, I want to convert it into a dynamic dictionary.
result = [
{
"standard": "119",
"score": "0",
"type": "assignment",
"student": "4"
},
{
"standard": "119",
"score": "0",
"type": "assignment",
"student": "5"
},
{
"standard": "118",
"score": "0",
"type": "assessment",
"student": "4"
}
]
I want to create one function conv_to_nested_dict(*args,data), which convertes all list of key to dictonary dynamically.
For example : conv_to_nested_dict(['standard','student'],result) should give op :
{
"118": {
"4": [{
"score": "0",
"type": "assessment"
}]
},
"119": {
"4": [{
"score": "0",
"type": "assignment"
}],
"5": [{
"score": "0",
"type": "assignment"
}]
}
}
conv_to_nested_dict(['standard','type'],result)
{
"118": {
"assessment": [{
"score": 0,
"student": "4"
}]
},
"119": {
"assignment": [{
"score": 0,
"student": "4"
},{
"score": 0,
"student": "5"
}]
}
}
This is a general idea.
def conf_to_nested_dict(keys, result):
R = {}
for record in result:
node = R
for key in keys[:-1]:
kv = record[key]
next_node = node.get(kv, {})
node[kv] = next_node
node = next_node
last_node = node.get(record[keys[-1]], [])
last_node.append(record)
node[record[keys[-1]]] = last_node
return R
#R is your structure
result is your source array, keys are the keys by which you want to group results. Iterate over results, for each record - create a tree structure based on key values ( record[key] ). For the last key - create a list and append the record to it.

Categories