请向下滚动以获得更新、更快的解决方案
这是一个较老的问题,但我整晚都在努力争取类似情况下的满意结果,我想出了这个:
import json
import pandas
def cross_join(left, right):
return left.assign(key=1).merge(right.assign(key=1), on='key', how='outer').drop('key', 1)
def json_to_dataframe(data_in):
def to_frame(data, prev_key=None):
if isinstance(data, dict):
df = pandas.DataFrame()
for key in data:
df = cross_join(df, to_frame(data[key], prev_key + '.' + key))
elif isinstance(data, list):
df = pandas.DataFrame()
for i in range(len(data)):
df = pandas.concat([df, to_frame(data[i], prev_key)])
else:
df = pandas.DataFrame({prev_key[1:]: [data]})
return df
return to_frame(data_in)
if __name__ == '__main__':
with open('somefile') as json_file:
json_data = json.load(json_file)
df = json_to_dataframe(json_data)
df.to_csv('data.csv', mode='w')
解释:
The 交叉连接函数是我发现做笛卡尔积的一种巧妙方法。 (信用:here https://stackoverflow.com/questions/53699012/performant-cartesian-product-cross-join-with-pandas)
The json_to_dataframe函数使用 pandas 数据框执行逻辑。就我而言,json 嵌套很深,我想拆分字典键:值对放入列中,但是我想将列表转换为列的行-- 因此是 concat -- 然后我将其与上层交叉连接,从而将记录数相乘,以便列表中的每个值都有自己的行,而前面的列是相同的。
递归性会创建与下面的堆栈交叉连接的堆栈,直到返回最后一个堆栈。
然后,使用表格格式的数据框,可以使用以下命令轻松转换为 CSV“df.to_csv()”数据框对象方法。
这应该适用于深度嵌套的 JSON,能够通过上述逻辑将所有 JSON 规范化为行。
我希望有一天这会对某人有所帮助。只是想回馈这个很棒的社区。
-------------------------------------------------- -------------------------------------------
稍后编辑:新解决方案
我回到这个话题,因为虽然 dataframe 选项有点工作,但应用程序花了几分钟来解析不太大的 JSON 数据。因此,我想到做数据框所做的事情,但由我自己来做:
from copy import deepcopy
import pandas
def cross_join(left, right):
new_rows = [] if right else left
for left_row in left:
for right_row in right:
temp_row = deepcopy(left_row)
for key, value in right_row.items():
temp_row[key] = value
new_rows.append(deepcopy(temp_row))
return new_rows
def flatten_list(data):
for elem in data:
if isinstance(elem, list):
yield from flatten_list(elem)
else:
yield elem
def json_to_dataframe(data_in):
def flatten_json(data, prev_heading=''):
if isinstance(data, dict):
rows = [{}]
for key, value in data.items():
rows = cross_join(rows, flatten_json(value, prev_heading + '.' + key))
elif isinstance(data, list):
rows = []
for item in data:
[rows.append(elem) for elem in flatten_list(flatten_json(item, prev_heading))]
else:
rows = [{prev_heading[1:]: data}]
return rows
return pandas.DataFrame(flatten_json(data_in))
if __name__ == '__main__':
json_data = {
"id": "0001",
"type": "donut",
"name": "Cake",
"ppu": 0.55,
"batters":
{
"batter":
[
{"id": "1001", "type": "Regular"},
{"id": "1002", "type": "Chocolate"},
{"id": "1003", "type": "Blueberry"},
{"id": "1004", "type": "Devil's Food"}
]
},
"topping":
[
{"id": "5001", "type": "None"},
{"id": "5002", "type": "Glazed"},
{"id": "5005", "type": "Sugar"},
{"id": "5007", "type": "Powdered Sugar"},
{"id": "5006", "type": "Chocolate with Sprinkles"},
{"id": "5003", "type": "Chocolate"},
{"id": "5004", "type": "Maple"}
],
"something": []
}
df = json_to_dataframe(json_data)
print(df)
OUTPUT:
id type name ppu batters.batter.id batters.batter.type topping.id topping.type
0 0001 donut Cake 0.55 1001 Regular 5001 None
1 0001 donut Cake 0.55 1001 Regular 5002 Glazed
2 0001 donut Cake 0.55 1001 Regular 5005 Sugar
3 0001 donut Cake 0.55 1001 Regular 5007 Powdered Sugar
4 0001 donut Cake 0.55 1001 Regular 5006 Chocolate with Sprinkles
5 0001 donut Cake 0.55 1001 Regular 5003 Chocolate
6 0001 donut Cake 0.55 1001 Regular 5004 Maple
7 0001 donut Cake 0.55 1002 Chocolate 5001 None
8 0001 donut Cake 0.55 1002 Chocolate 5002 Glazed
9 0001 donut Cake 0.55 1002 Chocolate 5005 Sugar
10 0001 donut Cake 0.55 1002 Chocolate 5007 Powdered Sugar
11 0001 donut Cake 0.55 1002 Chocolate 5006 Chocolate with Sprinkles
12 0001 donut Cake 0.55 1002 Chocolate 5003 Chocolate
13 0001 donut Cake 0.55 1002 Chocolate 5004 Maple
14 0001 donut Cake 0.55 1003 Blueberry 5001 None
15 0001 donut Cake 0.55 1003 Blueberry 5002 Glazed
16 0001 donut Cake 0.55 1003 Blueberry 5005 Sugar
17 0001 donut Cake 0.55 1003 Blueberry 5007 Powdered Sugar
18 0001 donut Cake 0.55 1003 Blueberry 5006 Chocolate with Sprinkles
19 0001 donut Cake 0.55 1003 Blueberry 5003 Chocolate
20 0001 donut Cake 0.55 1003 Blueberry 5004 Maple
21 0001 donut Cake 0.55 1004 Devil's Food 5001 None
22 0001 donut Cake 0.55 1004 Devil's Food 5002 Glazed
23 0001 donut Cake 0.55 1004 Devil's Food 5005 Sugar
24 0001 donut Cake 0.55 1004 Devil's Food 5007 Powdered Sugar
25 0001 donut Cake 0.55 1004 Devil's Food 5006 Chocolate with Sprinkles
26 0001 donut Cake 0.55 1004 Devil's Food 5003 Chocolate
27 0001 donut Cake 0.55 1004 Devil's Food 5004 Maple
根据上面的内容,好吧,交叉连接函数的作用与数据帧解决方案几乎相同,但没有数据帧,因此速度更快。
我添加了展平列表生成器,因为我想确保 JSON 数组都很好并且扁平化,然后作为单个字典列表提供,其中包含分配给每个列表的值之前的一次迭代中的前一个键。这几乎模仿了pandas.concat在这种情况下的行为。
main函数中的逻辑,json_to_dataframe然后就和以前一样了。需要改变的只是将数据帧作为编码函数执行的操作。
另外,在数据帧解决方案中,我没有将前一个标题附加到嵌套对象中,但除非您 100% 确定列名称中没有冲突,否则它几乎是强制性的。
我希望这有帮助 :)。
EDIT: 修改了交叉连接函数来处理嵌套列表为空的情况,基本上保持先前的结果集不变。即使在示例 JSON 数据中添加空 JSON 列表后,输出也不会更改。谢谢你, @纳兹穆斯·萨基布指出这一点。