Условие
JSON приходит с произвольной структурой: словари внутри списков внутри словарей до 5 уровней. Нужно вытащить все пары key:value (где value — скаляр) с полным путём в виде a.b[0].c.
Решение
Рекурсивный обход
def flatten_json(obj, prefix=''):
rows = []
if isinstance(obj, dict):
for k, v in obj.items():
new_prefix = f'{prefix}.{k}' if prefix else k
rows.extend(flatten_json(v, new_prefix))
elif isinstance(obj, list):
for i, v in enumerate(obj):
rows.extend(flatten_json(v, f'{prefix}[{i}]'))
else:
rows.append((prefix, obj))
return rows
# Использование
import json
data = json.load(open('payload.json'))
flat = flatten_json(data)
df = pd.DataFrame(flat, columns=['path', 'value'])Альтернатива — flatten_json или pandas.json_normalize
# json_normalize плющит словари, но не списки скаляров
import pandas as pd
pd.json_normalize(data, sep='.')
# pip install flatten-json
from flatten_json import flatten
flat_dict = flatten(data, separator='.')
df = pd.DataFrame([flat_dict])Потоковая обработка большого JSON
Если файл 5 ГБ — json.load упадёт. ijson парсит по токенам:
import ijson
with open('big.json', 'rb') as f:
for obj in ijson.items(f, 'items.item'):
process(obj)Подводные камни
- Рекурсия глубже 1000 уровней упирается в
sys.getrecursionlimit()— для глубоких JSON делать итеративно через стек. - Числа в JSON — int или float; даты — строки. После flatten типы потеряются, нужно типизировать.
- Списки разной длины в массиве объектов превращают плоскую таблицу в «рваную». Решение:
explodeили раздельные таблицы. - NaN в JSON нет — обычно null, который json.load даёт как None.
pd.DataFrameсам конвертирует в NaN/None в зависимости от dtype. - UTF-8 BOM в начале файла ломает json.load — открывайте с
encoding='utf-8-sig'.
Эталонный ответ
Рекурсивный обход с накоплением полного пути (a.b[0].c) либо json_normalize для регулярных структур. Для гигабайтных JSON — ijson потоково.