멀티레벨/네스트된 JSON을 평평하게 하는 방법
JSON을 CSV 파일로 변환하려고 합니다.이 파일은 향후 분석에 사용할 수 있습니다.JSON 파일을 변환할 때 중첩된 dict/list가 상당히 많다는 것이 구조상의 문제입니다.
나는 판다를 이용하려고 했다.json_normalize()단, 1단계만 평평해집니다.
import json
import pandas as pd
from pandas.io.json import json_normalize
from cs import CloudStack
api_key = xxxx
secret = xxxx
endpoint = xxxx
cs = CloudStack(endpoint=endpoint,
key=api_key,
secret=secret)
virtual_machines = cs.virtMach()
test = json_normalize(virtual_machines["virtualmachine"])
test.to_csv("test.csv", sep="|", index=False)
단일(이 경우 가상 머신) 엔트리에 대해 CSV 파일에 대한 단일 줄 입력을 생성할 수 있도록 전체 JSON 파일을 조정하는 방법을 알고 계십니까?여기에 게시된 몇 가지 해결책을 시도했지만, 결과는 항상 1단계에 불과했습니다.
다음으로 JSON의 예를 나타냅니다(이 예에서는 "security group"과 "nic" 출력이 JSON 형식으로 표시됩니다).
{
"count": 13,
"virtualmachine": [
{
"id": "1082e2ed-ff66-40b1-a41b-26061afd4a0b",
"name": "test-2",
"displayname": "test-2",
"securitygroup": [
{
"id": "9e649fbc-3e64-4395-9629-5e1215b34e58",
"name": "test",
"tags": []
}
],
"nic": [
{
"id": "79568b14-b377-4d4f-b024-87dc22492b8e",
"networkid": "05c0e278-7ab4-4a6d-aa9c-3158620b6471"
},
{
"id": "3d7f2818-1f19-46e7-aa98-956526c5b1ad",
"networkid": "b4648cfd-0795-43fc-9e50-6ee9ddefc5bd"
"traffictype": "Guest"
}
],
"hypervisor": "KVM",
"affinitygroup": [],
"isdynamicallyscalable": false
}
]
}
다음 기능을 사용했습니다(자세한 내용은 이쪽).
def flatten_data(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 + str(i) + '_')
i += 1
else:
out[name[:-1]] = x
flatten(y)
return out
이것은 불행히도 JSON 전체를 완전히 평평하게 만듭니다.즉, 다단계 JSON(많은 중첩된 사전)이 있는 경우 모든 것을 한 줄로 평평하게 만들 수 있습니다.
결국 제가 사용한 건json_normalize()그리고 내가 필요로 하는 구조를 지정했다.그 방법의 좋은 예시는, 이쪽에서 찾을 수 있습니다.
https://stackoverflow.com/a/62186053/4355695로부터의 크로스 투고(그 후, 한층 더 적응):https://github.com/ScriptSmith/socialreaper/blob/master/socialreaper/tools.py#L8에서는, @roneo에 의한 리스트의 코멘트의 실장으로부터 @Imran에 의한 회답에의 실장을 발견했습니다.
빈 목록과 빈 받아쓰기를 잡기 위해 수표를 추가했습니다.또, 이 기능을 정확하게 이해할 수 있도록 프린트 라인도 추가했습니다.다음 설정을 통해 인쇄 스테이트먼트를 끌 수 있습니다.crumbs=False
from collections import MutableMapping
crumbs = True
def flatten(dictionary, parent_key=False, separator='.'):
"""
Turn a nested dictionary into a flattened dictionary
:param dictionary: The dictionary to flatten
:param parent_key: The string to prepend to dictionary's keys
:param separator: The string used to separate flattened keys
:return: A flattened dictionary
"""
items = []
for key, value in dictionary.items():
if crumbs: print('checking:',key)
new_key = str(parent_key) + separator + key if parent_key else key
if isinstance(value, MutableMapping):
if crumbs: print(new_key,': dict found')
if not value.items():
if crumbs: print('Adding key-value pair:',new_key,None)
items.append((new_key,None))
else:
items.extend(flatten(value, new_key, separator).items())
elif isinstance(value, list):
if crumbs: print(new_key,': list found')
if len(value):
for k, v in enumerate(value):
items.extend(flatten({str(k): v}, new_key, separator).items())
else:
if crumbs: print('Adding key-value pair:',new_key,None)
items.append((new_key,None))
else:
if crumbs: print('Adding key-value pair:',new_key,value)
items.append((new_key, value))
return dict(items)
테스트:
ans = flatten({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3], 'e':{'f':[], 'g':{}} })
print('\nflattened:',ans)
출력:
checking: a
Adding key-value pair: a 1
checking: c
c : dict found
checking: a
Adding key-value pair: c.a 2
checking: b
c.b : dict found
checking: x
Adding key-value pair: c.b.x 5
checking: y
Adding key-value pair: c.b.y 10
checking: d
d : list found
checking: 0
Adding key-value pair: d.0 1
checking: 1
Adding key-value pair: d.1 2
checking: 2
Adding key-value pair: d.2 3
checking: e
e : dict found
checking: f
e.f : list found
Adding key-value pair: e.f None
checking: g
e.g : dict found
Adding key-value pair: e.g None
flattened: {'a': 1, 'c.a': 2, 'c.b.x': 5, 'c.b.y': 10, 'd.0': 1, 'd.1': 2, 'd.2': 3, 'e.f': None, 'e.g': None}
내가 필요한 일을 하는 앤드:복잡한 Json을 던져주면 더 잘 풀릴거야빈 목록도 처리하기 위해 원래 코드에 체크를 추가했습니다.
첫 번째 평탄함수를 발견한 https://github.com/ScriptSmith의 크레딧입니다.
OP의 샘플 json을 테스트하는 중 출력은 다음과 같습니다.
{'count': 13,
'virtualmachine.0.id': '1082e2ed-ff66-40b1-a41b-26061afd4a0b',
'virtualmachine.0.name': 'test-2',
'virtualmachine.0.displayname': 'test-2',
'virtualmachine.0.securitygroup.0.id': '9e649fbc-3e64-4395-9629-5e1215b34e58',
'virtualmachine.0.securitygroup.0.name': 'test',
'virtualmachine.0.securitygroup.0.tags': None,
'virtualmachine.0.nic.0.id': '79568b14-b377-4d4f-b024-87dc22492b8e',
'virtualmachine.0.nic.0.networkid': '05c0e278-7ab4-4a6d-aa9c-3158620b6471',
'virtualmachine.0.nic.1.id': '3d7f2818-1f19-46e7-aa98-956526c5b1ad',
'virtualmachine.0.nic.1.networkid': 'b4648cfd-0795-43fc-9e50-6ee9ddefc5bd',
'virtualmachine.0.nic.1.traffictype': 'Guest',
'virtualmachine.0.hypervisor': 'KVM',
'virtualmachine.0.affinitygroup': None,
'virtualmachine.0.isdynamicallyscalable': False}
따라서 '태그' 키와 '선호도 그룹' 키도 처리되어 출력에 추가됩니다.원래 암호는 그들을 누락시키는 거였어요.
2021-05-30 : 갱신: 컬렉션.Mutable Mapping이 collections.abc로 변경되었습니다.변경 가능한 매핑
2023-01-11 : 두 번째 항목에 구분arg를 편집, 추가하였습니다.@MHebes에 의한 어드바이스에 의한 extend() 콜
IMO가 승인한 응답은 JSON 어레이를 올바르게 처리하지 않습니다.
JSON 객체의 값이 어레이인 경우 다음과 같은 객체 배열로 평탄화해야 합니다.
{'a': [1, 2]} -> [{'a': 1}, {'a': 2}]
인덱스를 키에 추가하는 대신.
그리고 중첩된 개체는 다음과 같이 키를 연결하여 평탄화해야 합니다(예를 들어 점을 구분자로 함).
{'a': {'b': 1}} -> {'a.b': 1}
(이 작업은 받아들여진 1개로 올바르게 이루어집니다).
이러한 모든 요건을 충족하기 위해 다음과 같은 작업을 수행하게 되었습니다(CPython 3.5.3에서 개발 및 사용).
from functools import (partial,
singledispatch)
from itertools import chain
from typing import (Dict,
List,
TypeVar)
Serializable = TypeVar('Serializable', None, int, bool, float, str,
dict, list, tuple)
Array = List[Serializable]
Object = Dict[str, Serializable]
def flatten(object_: Object,
*,
path_separator: str = '.') -> Array[Object]:
"""
Flattens given JSON object into list of objects with non-nested values.
>>> flatten({'a': 1})
[{'a': 1}]
>>> flatten({'a': [1, 2]})
[{'a': 1}, {'a': 2}]
>>> flatten({'a': {'b': None}})
[{'a.b': None}]
>>> flatten({'a': [1, 2], 'b': []})
[{'a': 1}, {'a': 2}]
"""
keys = set(object_)
result = [dict(object_)]
while keys:
key = keys.pop()
new_result = []
for index, record in enumerate(result):
try:
value = record[key]
except KeyError:
new_result.append(record)
else:
if isinstance(value, dict):
del record[key]
new_value = flatten_nested_objects(
value,
prefix=key + path_separator,
path_separator=path_separator
)
keys.update(new_value.keys())
new_result.append({**new_value, **record})
elif isinstance(value, list):
del record[key]
new_records = [
flatten_nested_objects(sub_value,
prefix=key + path_separator,
path_separator=path_separator)
for sub_value in value
]
keys.update(chain.from_iterable(map(dict.keys,
new_records)))
if new_records:
new_result.extend({**new_record, **record}
for new_record in new_records)
else:
new_result.append(record)
else:
new_result.append(record)
result = new_result
return result
@singledispatch
def flatten_nested_objects(object_: Serializable,
*,
prefix: str = '',
path_separator: str) -> Object:
return {prefix[:-len(path_separator)]: object_}
@flatten_nested_objects.register(dict)
def _(object_: Object,
*,
prefix: str = '',
path_separator: str) -> Object:
result = dict(object_)
for key in list(result):
result.update(flatten_nested_objects(result.pop(key),
prefix=(prefix + key
+ path_separator),
path_separator=path_separator))
return result
@flatten_nested_objects.register(list)
def _(object_: Array,
*,
prefix: str = '',
path_separator: str) -> Object:
return {prefix[:-len(path_separator)]: list(map(partial(
flatten_nested_objects,
path_separator=path_separator),
object_))}
다른 사람이 여기에 있는 것을 발견하고 이후의 프로그램 처리에 더 적합한 솔루션을 찾고 있는 경우:
리스트를 평평하게 하면, 리스트의 길이등의 헤더를 처리할 필요가 있습니다.예를 들어 2개의 요소 목록이 있는 경우 각 유효한 잠재적 데이터 행을 생성하는 4개의 행이 생성되는 솔루션을 원했습니다(실제 예는 아래 참조).
class MapFlattener:
def __init__(self):
self.headings = []
self.rows = []
def add_rows(self, headings, rows):
self.headings = [*self.headings, *headings]
if self.rows:
new_rows = []
for base_row in self.rows:
for row in rows:
new_rows.append([*base_row, *row])
self.rows = new_rows
else:
self.rows = rows
def __call__(self, mapping):
for heading, value in mapping.items():
if isinstance(value, Mapping):
sub_headings, sub_rows = MapFlattener()(value)
sub_headings = [f'{heading}:{sub_heading}' for sub_heading in sub_headings]
self.add_rows(sub_headings, sub_rows)
continue
if isinstance(value, list):
self.add_rows([heading], [[e] for e in value])
continue
self.add_rows([heading], [[value]])
return self.headings, self.rows
def map_flatten(mapping):
return MapFlattener()(mapping)
이것에 의해, 관계 데이터에 맞추어 출력이 보다 많이 작성됩니다.
In [22]: map_flatten({'l': [1,2]})
Out[22]: (['l'], [[1], [2]])
In [23]: map_flatten({'l': [1,2], 'n': 7})
Out[23]: (['l', 'n'], [[1, 7], [2, 7]])
In [24]: map_flatten({'l': [1,2], 'n': 7, 'o': {'a': 1, 'b': 2}})
Out[24]: (['l', 'n', 'o:a', 'o:b'], [[1, 7, 1, 2], [2, 7, 1, 2]])
In [25]: map_flatten({'top': {'middle': {'bottom': [0, 1]}, 'ml': ['A', 'B']}, 'l': ['a', 'b']})
Out[25]:
(['top:middle:bottom', 'top:ml', 'l'],
[[0, 'A', 'a'],
[0, 'A', 'b'],
[0, 'B', 'a'],
[0, 'B', 'b'],
[1, 'A', 'a'],
[1, 'A', 'b'],
[1, 'B', 'a'],
[1, 'B', 'b']])
이는 스프레드시트 등에서 csv를 사용하고 있으며 평탄화된 데이터를 처리해야 하는 경우에 특히 유용합니다.
BFS 접근 방식을 사용했는데, val이 dict type인 경우에만 큐에 저장(부모, val)합니다.
def flattern_json(d):
if len(d) == 0:
return {}
from collections import deque
q = deque()
res = dict()
for key, val in d.items(): # This loop push the top most keys and values into queue.
if not isinstance(val, dict): # If it's not dict
if isinstance(val, list): # If it's list then check list values if it contains dict object.
temp = list() # Creating temp list for storing the values that we will need which are not dict.
for v in val:
if not isinstance(v, dict):
temp.append(v)
else:
q.append((key, v)) # if it's value is dict type then we push along with parent which is key.
if len(temp) > 0:
res[key] = temp
else:
res[key] = val
else:
q.append((key, val))
while q:
k, v = q.popleft() # Taking parent and the value out of queue
for key, val in v.items():
new_parent = k + "_" + key # New parent will be old parent_currentval
if isinstance(val, list):
temp = list()
for v in val:
if not isinstance(v, dict):
temp.append(v)
else:
q.append((new_parent, v))
if len(temp) >= 0:
res[new_parent] = temp
elif not isinstance(val, dict):
res[new_parent] = val
else:
q.append((new_parent, val))
return res
0 1 목록 인덱싱을 사용하는 대신 JSON을 평평하게 만들기 위해 제공된 JSON과 함께 작동합니다.
from pprint import pprint
print(pprint.pprint(flattern_json(d)))
다음과 같은 출력을 얻을 수 있습니다.
{'count': 13,
'virtualmachine_affinitygroup': [],
'virtualmachine_displayname': 'test-2',
'virtualmachine_hypervisor': 'KVM',
'virtualmachine_id': '1082e2ed-ff66-40b1-a41b-26061afd4a0b',
'virtualmachine_isdynamicallyscalable': False,
'virtualmachine_name': 'test-2',
'virtualmachine_nic': [],
'virtualmachine_nic_id': '3d7f2818-1f19-46e7-aa98-956526c5b1ad',
'virtualmachine_nic_networkid': 'b4648cfd-0795-43fc-9e50-6ee9ddefc5bd',
'virtualmachine_nic_traffictype': 'Guest',
'virtualmachine_securitygroup': [],
'virtualmachine_securitygroup_id': '9e649fbc-3e64-4395-9629-5e1215b34e58',
'virtualmachine_securitygroup_name': 'test',
'virtualmachine_securitygroup_tags': []}
이 간단한 함수를 사용하여 데이터를 정규화하고 json으로 평탄화합니다.리스트, 딕트, 튜플을 받아 json으로 고정합니다.
def normalize_data_to_json(raw_data: [list, dict, tuple], parent=""):
from datetime import datetime
from decimal import Decimal
result = {}
# key name normalise to snake case (single underscore)
parent = parent.lower().replace(" ", "_") if isinstance(parent, str) else parent
if isinstance(parent, str) and parent.startswith("__"):
# if parent has no parent remove double underscore and treat as int if digit else as str
# treating as int is better if passed data is a list so you output is index based dict
parent = int(parent.lstrip("_")) if parent.lstrip("_").isdigit() else parent.lstrip("_")
# handle str, int, float, and decimal.
# you can easily add more data types as er your data
if type(raw_data) in [str, int, float, Decimal]:
result[parent] = float(raw_data) if isinstance(raw_data, Decimal) else raw_data
# normalise datetime object
elif isinstance(raw_data, datetime):
result[parent] = raw_data.strftime("%Y-%m-%d %H:%M:%S")
# normalise dict and all nested dicts.
# all nests are joined with double underscore to identify parent key name with it's children
elif isinstance(raw_data, dict):
for k, v in raw_data.items():
k = f'{parent}__{k}' if parent else k
result.update(normalize_data_to_json(v, parent=k))
# normalise list and tuple
elif type(raw_data) in [list, tuple]:
for i, sub_item in enumerate(raw_data, start=1):
result.update(normalize_data_to_json(sub_item, f"{parent}__{i}"))
# any data which did not matched above data types, normalise them using it's __str__
else:
result[parent] = str(raw_data)
return result
나는 많은 다양한 방법들을 읽어왔다.복잡한 중첩된 JSON에서 작동한 유일한 시스템입니다.이것은 중첩된 JSON을 평평하게 만들고 원하는 요소를 쉽게 걸러낼 수 있도록 팬더 데이터 프레임으로 변환하는 것입니다.
import json
import pprint
import pandas as pd
from flatten_json import flatten
with open('sth.json') as json_file:
nested_json = json.load(json_file)
nested_json = nested_json["_via_img_metadata"]
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 + str(i) + '_')
i += 1
else:
out[name[:-1]] = x
return out
df = pd.Series(flatten(nested_json)).to_frame()
jsonpath 형식으로 출력:
def convert(f):
out = {}
def flatten(x, name=None):
if type(x) is dict:
for a in x:
val = '.'.join((name, a)) if name else a
flatten(x[a], val)
elif type(x) is list:
for (i, a) in enumerate(x):
flatten(a, name + f'[{str(i)}]')
else:
out[name] = x if x else ""
flatten(f)
return out
사전을 여기로 넘기면 됩니다.
def getKeyValuePair(dic,master_dic = {},master_key = None):
keys = list(dic.keys())
for key in keys:
if type(dic[key]) == dict:
getKeyValuePair(dic[key],master_dic = master_dic,master_key = key)
else:
if master_key == None:
master_dic[key] = dic[key]
else:
master_dic[str(master_key)+'_'+str(key)] = dic[key]
return master_dic
언급URL : https://stackoverflow.com/questions/51359783/how-to-flatten-multilevel-nested-json
'programing' 카테고리의 다른 글
| SQL Server의 datetime 필드의 기본값을 타임스탬프에 추가합니다. (0) | 2023.04.06 |
|---|---|
| 클래스 vsVB 모듈그물 (0) | 2023.04.06 |
| AngularJS 데이터 바인딩 유형 (0) | 2023.04.06 |
| 리액션: 아이콘을 표시하지 않음 (0) | 2023.04.06 |
| Wordpress 기본 갤러리 출력 변경 (0) | 2023.04.06 |