Flatten nested List - python

Not sure if I'm asking this in the right place or if I'm understanding this correctly. I need change the dictionary so that the occurrence field is added on to the availability key. For example: "Availability.Occurrence": "Daily"
BEFORE
test_data = {
"testDate": "2018-11-19 21:00:00",
"testMessage": "This is a test message",
"testStatus": "Warning",
"Results": [
{
"Availability": {
"Occurence": "Daily"
},
"OldestRefreshDate": "2018-11-15 15:40:57 EST",
"TableName": "test"
}
],
"TaskId": "CheckSourceRefreshDates"
}
AFTER
test_data = {
"testDate": "2018-11-19 21:00:00",
"testMessage": "This is a test message",
"testStatus": "Warning",
"Results": [
{
"Availability.Occurrence": "Daily",
"OldestRefreshDate": "2018-11-15 15:40:57 EST",
"TableName": "test"
}
],
"TaskId": "CheckSourceRefreshDates"
}
I am new to Python just trying to figure all this out. Any help is appreciated.
Heres the function that I tried using, however it only flattens the entire dictionary.
def flatten_json(y):
out = {}
def flatten(x, name=''):
if type(x) is dict:
for a in x:
flatten(x[a], name + a + '.')
elif type(x) is list:
i = 0
for a in x:
flatten(a, name + '.')
i += 1
else:
out[name[:-1]] = x
flatten(y)
return out

Related

Create complex object in Python based on property names in dot notation

I am trying to create a complex object based on metadata I have. It is an array of attributes which I am iterating and trying to create a dict. For example below is the array:
[
"itemUniqueId",
"itemDescription",
"manufacturerInfo[0].manufacturer.value",
"manufacturerInfo[0].manufacturerPartNumber",
"attributes.noun.value",
"attributes.modifier.value",
"attributes.entityAttributes[0].attributeName",
"attributes.entityAttributes[0].attributeValue",
"attributes.entityAttributes[0].attributeUOM",
"attributes.entityAttributes[1].attributeName",
"attributes.entityAttributes[1].attributeValue",
"attributes.entityAttributes[1].attributeUOM",
]
This array should give an output as below:
{
"itemUniqueId": "",
"itemDescription": "",
"manufacturerInfo": [
{
"manufacturer": {
"value": ""
},
"manufacturerPartNumber": ""
}
],
"attributes": {
"noun": {
"value": ""
},
"modifier": {
"value": ""
},
"entityAttributes": [
{
"attributeName": "",
"attributeValue": "",
"attributeUOM": ""
},
{
"attributeName": "",
"attributeValue": "",
"attributeUOM": ""
}
]
}
}
I have written this logic but unable to get the desired output. It should work on both object and array given the metadata.
source_json = [
"itemUniqueId",
"itemDescription",
"manufacturerInfo[0].manufacturer.value",
"manufacturerInfo[0].manufacturerPartNumber",
"attributes.noun.value",
"attributes.modifier.value",
"attributes.entityAttributes[0].attributeName",
"attributes.entityAttributes[0].attributeValue",
"attributes.entityAttributes[0].attributeUOM",
"attributes.entityAttributes[1].attributeName",
"attributes.entityAttributes[1].attributeValue",
"attributes.entityAttributes[1].attributeUOM",
]
for row in source_json:
propertyNames = row.split('.')
temp = ''
parent = {}
parentArr = []
parentObj = {}
# if len(propertyNames) > 1:
arrLength = len(propertyNames)
for i, (current) in enumerate(zip(propertyNames)):
if i == 0:
if '[' in current:
parent[current]=parentArr
else:
parent[current] = parentObj
temp = current
if i > 0 and i < arrLength - 1:
if '[' in current:
parent[current] = parentArr
else:
parent[current] = parentObj
temp = current
if i == arrLength - 1:
if '[' in current:
parent[current] = parentArr
else:
parent[current] = parentObj
temp = current
# temp[prev][current] = ""
# finalMapping[target] = target
print(parent)
There's a similar question at Convert Dot notation string into nested Python object with Dictionaries and arrays where the accepted answer works for this question, but has unused code paths (e.g. isInArray) and caters to unconventional conversions expected by that question:
❓ "arrOne[0]": "1,2,3" → "arrOne": ["1", "2", "3"] instead of
✅ "arrOne[0]": "1,2,3" → "arrOne": ["1,2,3"] or
✅ "arrOne[0]": "1", "arrOne[1]": "2", "arrOne[2]": "3" → "arrOne": ["1", "2", "3"]
Here's a refined implementation of the branch function:
def branch(tree, path, value):
key = path[0]
array_index_match = re.search(r'\[([0-9]+)\]', key)
if array_index_match:
# Get the array index, and remove the match from the key
array_index = int(array_index_match[0].replace('[', '').replace(']', ''))
key = key.replace(array_index_match[0], '')
# Prepare the array at the key
if key not in tree:
tree[key] = []
# Prepare the object at the array index
if array_index == len(tree[key]):
tree[key].append({})
# Replace the object at the array index
tree[key][array_index] = value if len(path) == 1 else branch(tree[key][array_index], path[1:], value)
else:
# Prepare the object at the key
if key not in tree:
tree[key] = {}
# Replace the object at the key
tree[key] = value if len(path) == 1 else branch(tree[key], path[1:], value)
return tree
Usage:
VALUE = ''
def create_dict(attributes):
d = {}
for path_str in attributes:
branch(d, path_str.split('.'), VALUE)
return d
source_json = [
"itemUniqueId",
"itemDescription",
"manufacturerInfo[0].manufacturer.value",
"manufacturerInfo[0].manufacturerPartNumber",
"attributes.noun.value",
"attributes.modifier.value",
"attributes.entityAttributes[0].attributeName",
"attributes.entityAttributes[0].attributeValue",
"attributes.entityAttributes[0].attributeUOM",
"attributes.entityAttributes[1].attributeName",
"attributes.entityAttributes[1].attributeValue",
"attributes.entityAttributes[1].attributeUOM",
]
assert create_dict(source_json) == {
"itemUniqueId": "",
"itemDescription": "",
"manufacturerInfo": [
{
"manufacturer": {
"value": ""
},
"manufacturerPartNumber": ""
}
],
"attributes": {
"noun": {
"value": ""
},
"modifier": {
"value": ""
},
"entityAttributes": [
{
"attributeName": "",
"attributeValue": "",
"attributeUOM": ""
},
{
"attributeName": "",
"attributeValue": "",
"attributeUOM": ""
}
]
}
}
First we should iterate over whole list and store each 3rd attributes, after that we could change this struct to our desired output:
from typing import Dict, List
source_json = [
"attributes.entityAttributes[0].attributeName",
"attributes.entityAttributes[0].attributeValue",
"attributes.entityAttributes[0].attributeUOM",
"attributes.entityAttributes[1].attributeName",
"attributes.entityAttributes[1].attributeValue",
"attributes.entityAttributes[1].attributeUOM",
"attributes.entityAttributes[2].attributeName"
]
def accumulate(source: List) -> Dict:
accumulator = {}
for v in source:
vs = v.split(".")
root_attribute = vs[0]
if not root_attribute in accumulator:
accumulator[root_attribute] = {}
i = vs[1].rfind('[')
k = (vs[1][:i], vs[1][i+1:-1])
if not k in accumulator[root_attribute]:
accumulator[root_attribute][k] = {}
accumulator[root_attribute][k][vs[2]] = ""
return accumulator
def get_result(accumulated: Dict) -> Dict:
result = {}
for k, v in accumulated.items():
result[k] = {}
for (entity, idx), v1 in v.items():
if not entity in result[k]:
result[k][entity] = []
if len(v1) == 3:
result[k][entity].append(v1)
return result
print(get_result(accumulate(source_json)))
The output will be:
{
'attributes':
{
'entityAttributes':
[
{
'attributeName': '',
'attributeValue': '',
'attributeUOM': ''
},
{'attributeName': '',
'attributeValue': '',
'attributeUOM': ''
}
]
}
}
In accumulate function we store 3rd level attributes in Dict with (entityAttributes, 0) ... (entityAttributes, 2) keys.
In get_result function we convert Dict with (entityAttributes, 0) ... (entityAttributes, 2) keys to Dict from string to List.
How about something like this:
import re
import json
source_json = [
"attributes.entityAttributes[0].attributeName",
"attributes.entityAttributes[0].attributeValue",
"attributes.entityAttributes[0].attributeUOM",
"attributes.entityAttributes[1].attributeName",
"attributes.entityAttributes[1].attributeValue",
"attributes.entityAttributes[1].attributeUOM",
"attributes.entityAttributes[2].attributeName"
]
def to_object(source_json):
def add_attribute(target, attribute_list):
head, tail = attribute_list[0], attribute_list[1:]
if tail:
add_attribute(target.setdefault(head,{}), tail)
else:
target[head] = ''
target = {}
for row in source_json:
add_attribute(target, re.split(r'[\.\[\]]+',row))
return target
print(json.dumps(to_object(source_json), indent=4))
Note that this will not exactly do what you requested. It interprets stores the array also as an object with keys '0' ... '2'. This makes it easier to implement and also more stable. What would you expect, when the input list missed the entries with entityAttributes[0]. Should the list include an empty element or something different. Anyway you save space by not including this element, which works only if you store the array in an object.
None of the answers provided so far strike me as very intuitive. Here's one way
to tackle the problem with three easy-to-understand functions.
Normalize inputs. First we need a function to normalize the inputs strings. Instead of rules-bearing strings like
'foo[0].bar' – where one must understand that integers
in square brackets imply a list – we want a simple tuple
of keys like ('foo', 0, 'bar').
def attribute_to_keys(a):
return tuple(
int(k) if k.isdigit() else k
for k in a.replace('[', '.').replace(']', '').split('.')
)
Build a uniform data structure. Second, we need a function to assemble a data structure consisting of dicts
of dicts of dicts ... all the way down.
def assemble_data(attributes):
data = {}
for a in attributes:
d = data
for k in attribute_to_keys(a):
d = d.setdefault(k, {})
return convert(data)
def convert(d):
# Just a placeholder for now.
return d
Convert the uniform data. Third, we need to implement a real version of the placeholder. Specifically, we
need it to recursively convert the uniform data structure into our ultimate
goal having (a) empty strings at leaf nodes, and (b) lists rather than dicts
whenever the dict keys are all integers. Note that this even fills in empty
list positions with an empty string (a contingency not covered in your problem
description; adjust as needed if you want a different behavior).
def convert(d):
if not d:
return ''
elif all(isinstance(k, int) for k in d):
return [convert(d.get(i)) for i in range(max(d) + 1)]
else:
return {k : convert(v) for k, v in d.items()}
You can use a custom builder class which implements __getattr__ and __getitem__ to gradually build the underlying object. This building can then be triggered by using eval on each of the attribute strings (note: eval is not safe for input from untrusted sources).
The following is an example implementation:
class Builder:
def __init__(self):
self.obj = None
def __getattr__(self, key):
if self.obj is None:
self.obj = {}
return self.obj.setdefault(key, Builder())
def __getitem__(self, index):
if self.obj is None:
self.obj = []
self.obj.extend(Builder() for _ in range(index+1-len(self.obj)))
return self.obj[index]
def convert(self):
if self.obj is None:
return ''
elif isinstance(self.obj, list):
return [v.convert() for v in self.obj]
elif isinstance(self.obj, dict):
return {k: v.convert() for k,v in self.obj.items()}
else:
assert False
attributes = [
'itemUniqueId',
'itemDescription',
'manufacturerInfo[0].manufacturer.value',
'manufacturerInfo[0].manufacturerPartNumber',
'attributes.noun.value',
'attributes.modifier.value',
'attributes.entityAttributes[0].attributeName',
'attributes.entityAttributes[0].attributeValue',
'attributes.entityAttributes[0].attributeUOM',
'attributes.entityAttributes[1].attributeName',
'attributes.entityAttributes[1].attributeValue',
'attributes.entityAttributes[1].attributeUOM',
]
builder = Builder()
for attr in attributes:
eval(f'builder.{attr}')
result = builder.convert()
import json
print(json.dumps(result, indent=4))
which gives the following output:
{
"itemUniqueId": "",
"itemDescription": "",
"manufacturerInfo": [
{
"manufacturer": {
"value": ""
},
"manufacturerPartNumber": ""
}
],
"attributes": {
"noun": {
"value": ""
},
"modifier": {
"value": ""
},
"entityAttributes": [
{
"attributeName": "",
"attributeValue": "",
"attributeUOM": ""
},
{
"attributeName": "",
"attributeValue": "",
"attributeUOM": ""
}
]
}
}

Get the name of a json object with Python

How can I get the name of json object so that I can match on it in an if statement?
def test():
device_config = api_get_conf()
routing_protocol = device_config['Cisco-IOS-XE-native:native']
if 'ospf' in routing_protocol:
print('It worked!')
else:
print('dunno')
The routing_protocol variable has the following information in it:
{"Cisco-IOS-XE-ospf:router-ospf": {
"ospf": {
"process-id": [
{
"id": 100,
"area": [
{
"area-id": 0,
"authentication": {
"message-digest": [
null
]
}
}
],
"network": [
{
"ip": "192.168.200.200",
"wildcard": "0.0.0.0",
"area": 0
}
]
}
]
}
}
}
I would like to match on only 'Cisco-IOS-XE-ospf:router-ospf' or 'ospf'. Any help on how I can do this would be appreciated.
def test():
device_config = api_get_conf()
# since we use get here, if we dont find it we set routing_protocol as False, easier to use on if
routing_protocol = device_config.get('Cisco-IOS-XE-native:native', False)
if routing_protocol:
if 'ospf' in routing_protocol:
print('It worked!')
print("ospf not a key")
else:
print('routing protocol not a key')
I'm not sure what you mean, but it looks like api_get_conf() returns Dictionary where "Cisco-IOS-XE-ospf:router-ospf" is first key, and its value is another dictionary where key is "ospf". If it's what you wants to compare then you can simply use .keys() method on routing_protocol dict.
def test():
device_config = api_get_conf()
routing_protocol = device_config['Cisco-IOS-XE-native:native']
if 'ospf' in routing_protocol.keys():
print('It worked!')
else:
print('dunno')

Decide JSON data python parsing flat

it's been a while since I've been stuck on a subject to which I can't find the desired solution.
Example: I have a json given like this:
{
"SECTION": {
"ID": 1,
"COMMENT" : "foo bar ",
"STRUCTURE" : {
"LIEN" : [
{
"from": "2020-01-01",
"to": "2020-01-03"
},
{
"from": "2020-01-04",
"to": "2999-01-07"
}
]
},
"CONTEXTE":{
"NATURE": {
"text": "lorem smdlk fjq lsjdf mqjsh dflkq hs dfhkq g"
}
}
}
}
I would like to have output, for example this:
{
"SECTION.ID": 1,
"SECTION.COMMENT": "foo bar ",
"SECTION.STRUCTURE.LIEN.from": "2020-01-01",
"SECTION.STRUCTURE.LIEN.to": "2020-01-03",
"SECTION.CONTEXTE.NATURE.text": "lorem smdlk fjq lsjdf mqjsh dflkq hs dfhkq g"
}
{
"SECTION.ID": 1,
"SECTION.COMMENT": "foo bar ",
"SECTION.STRUCTURE.LIEN.from": "2020-01-04",
"SECTION.STRUCTURE.LIEN.to": "2999-01-07",
"SECTION.CONTEXTE.NATURE.text": "lorem smdlk fjq lsjdf mqjsh dflkq hs dfhkq g"
}
Does anyone have any idea how I can do this in python? Thank you so much
I suggest you use the json Python module to convert the JSON object to a Python object. Then you can use recursion. If you are using Python 3.5 or later, the following code could be a good starting point:
import json
def flatten_helper(prefix, list_of_dict):
res = []
for i in list_of_dict:
res_dict={}
for k, v in i.items():
res_dict['.'.join([prefix,k])]=v
res.append(res_dict)
return res
def flatten(x):
if isinstance(x, list):
res = []
for ele in x:
res = res + flatten(ele)
return res
else:
res = [{}]
for k, v in x.items():
if (isinstance(v, dict) or isinstance(v,list)):
new_res = []
tempo = flatten(v)
for r in res:
for t in tempo:
new_res.append({**r, **t})
res = flatten_helper(k,new_res)
else:
for i, val in enumerate(res):
res[i][k]=v
return res
jsonobj = '{"SECTION": {"ID": 1, "COMMENT" : "foo bar ", "STRUCTURE" : { "LIEN" : [{"from": "2020-01-01", "to": "2020-01-03"}, {"from": "2020-01-04", "to": "2999-01-07" }]}, "CONTEXTE":{"NATURE": {"text": "lorem smdlk fjq lsjdf mqjsh dflkq hs dfhkq g"}}}}'
pyobj = json.loads(jsonobj)
res = flatten(pyobj)
Sounds like a classic case for recursion; aggregate path until you reach a "simple" value then write the pair "aggregated path"."key" : "value"

How to return all of elements from function?

I'm trying to create an Ansible module to use Batfish inside an Ansible playbook.
I'm comparing JSON values with defined variables in function. But it can compare only one JSON value and variable in the function. How do I use loop and return?
I have already tried extract values from each JSON and tried to compare with defined variable.
import json
json_list = {"batfish_result": [
{
"Action": {
"0": "DENY"
},
"Line_Content": {
"0": "no-match"
}
},
{
"Action": {
"0": "PERMIT"
},
"Line_Content": {
"0": "permit 10.20.0.0 255.255.255.0"
}
}
]
}
def main(json_list):
PASS = 'PASS'
FAIL = 'FAIL'
result = {}
result_list = []
action_num_list = []
condition_list = ['permit', 'permit']
jsons = json_list["batfish_result"]
for j in jsons:
print(j)
action = j['Action']
action_num = action["0"]
action_num_list.append(action_num)
for con in condition_list:
for action in action_num_list:
if action == con.upper():
result_list.append(PASS)
result['msg'] = result_list
else:
result_list.append(FAIL)
result['msg'] = result_list
return result
main(json_list)
It returns
{'msg': ['PASS', 'PASS']}
It should be comparing each action with each condition variable like this.
{ "msg": ['FAIL', 'PASS'] }
Finally I solved it like this;
import json
from pprint import pprint
json_list = {"batfish_result": [
{
"Action": {
"0": "DENY"
},
"Line_Content": {
"0": "no-match"
}
},
{
"Action": {
"0": "PERMIT"
},
"Line_Content": {
"0": "permit 10.20.0.0 255.255.255.0"
}
}
]
}
def main(json_list):
PASS = "PASS"
FAIL = "FAIL"
result = {}
result_list = []
action_num_list = []
condition_list = ["permit", "permit"]
jsons = json_list["batfish_result"]
for j in jsons:
action = j['Action']
action_num = action["0"]
action_num_list.append(action_num)
#[DENY, PERMIT]
for con in condition_list:
con = con
#for action in action_num_list:
for x, y in zip(condition_list, action_num_list):
if y == x.upper():
result_list.append(PASS)
result['msg'] = result_list
#if pprint(y) != pprint(x.upper()):
else:
result_list.append(FAIL)
result['msg'] = result_list
return result_list
main(json_list)
Because pprint always returns 'None'. So I had to remove pprint after debug and also I used loop too much.

Check if key/value is in JSON

With this code
import sense
import json
sense.api_key = '...'
node = sense.Node.retrieve('........')
feed = node.feeds.retrieve('presence')
events = feed.events.list(limit=1)
result = json.dumps(events,indent=1)
print result
I get a JSON-Feed like this:
{
"links": {...},
"objects": [
{
"profile": "GenStandard",
"feedUid": ".....",
"gatewayNodeUid": ".....",
"dateServer": "2015-02-28T09:57:22.337034",
"geometry": null,
"data": {
"body": "Present",
"code": 200
},
"signal": "-62",
"dateEvent": "2015-02-28T09:57:22.000000",
"type": "presence",
"payload": "2",
"nodeUid": "....."
}
],
"totalObjects": 875,
"object": "list"
}
How can I check if 'body' is 'present' (or 'code' is '200')? My script should return TRUE or FALSE
UPDATE
If I add this code as proposed in the answers it works fine:
d=json.loads(result)
def checkJson(jsonContents):
bodyFlag = True if "body" in jsonContents["objects"][0]["data"] and jsonContents["objects"][0]["data"]["body"] == "Present" else False
return bodyFlag
print checkJson(d)
You should also maybe check if the body key is actually there.
def checkJson(jsonContents):
bodyFlag = True if "body" in jsonContents["objects"][0]["data"] and jsonContents["objects"][0]["data"]["body"] == "Present" else False
codeFlag = True if "code" in jsonContents["objects"][0]["data"] and jsonContents["objects"][0]["data"]["code"] == 200 else False
return bodyFlag or codeFlag
print checkJson(result)
d = json.loads(results)
objs = d["objects"][0]
# see if any code is either == 200 or "body" is a key in the subdict
return any(x for x in (objs["data"]["code"] == 200,"body" in objs["data"]))

Categories

Resources