遇到多层嵌套json应优先用flatten_json库扁平化,而非手写递归;处理数组时需局部展开避免字段爆炸;csv导出须用utf-8-sig编码并统一字段集,确保excel正确显示中文及兼容缺失字段。

遇到多层嵌套JSON时,别硬写递归函数
直接用 json.loads() 读入后,面对 "data": {"user": {"profile": {"name": "Alice", "tags": ["a", "b"]}}} 这类结构,手写递归展开极易漏字段、错层级、崩栈。真实数据里还常混着 null、空列表、字典与列表交替嵌套——这时候优先考虑扁平化工具而非自己造轮子。
推荐用 flatten_json 库(pip install flatten-json),它默认用下划线拼接键名,对 None 和空列表也做了安全处理:
from flatten_json import flatten
import json
<p>with open("data.json") as f:
raw = json.load(f)
flat = flatten(raw, separator="_") # 得到 {'data_user_profile_name': 'Alice', 'data_user_profile_tags_0': 'a', ...}</p>
- 注意:默认
separator="_",若原始字段含下划线(如"api_key"),建议改用"|"避免歧义 - 遇到数组时,它自动加序号(
"tags_0","tags_1"),但若数组内对象也有嵌套,需确认是否要保留全部路径 - 如果 JSON 顶层是列表(如 [{...}, {...}]),得先遍历每个元素再
flatten,不能直接传整个列表
CSV写入前必须统一字段名集合
不同 JSON 对象字段不一致(比如有的有 "address",有的只有 "city")会导致 csv.DictWriter 报 ValueError: dict contains fields not in fieldnames。这不是数据问题,是写入逻辑没兜住。
正确做法是先扫描所有扁平化后的字典,收集全部键名,再补全缺失字段:
all_keys = set()
flattened_rows = []
for item in json_data: # 假设这是个列表
flat = flatten(item)
all_keys.update(flat.keys())
flattened_rows.append(flat)
<p>fieldnames = sorted(all_keys) # 排序让列顺序稳定
with open("output.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for row in flattened_rows:</p><h1>缺失字段自动填空字符串,避免 KeyError</h1><pre class="brush:python;toolbar:false;"> writer.writerow({k: row.get(k, "") for k in fieldnames})
-
row.get(k, "")比row[k]安全,尤其字段名含特殊字符或空格时 - 如果某字段值是列表(如扁平后仍剩
"items": ["x", "y"]),flatten_json默认不展开——得提前用max_depth=None参数或手动处理 - 中文字段名或含逗号的值,务必用
quoting=csv.QUOTE_MINIMAL,否则 CSV 解析会错位
数组字段导出成多行更实用
把 "orders": [{"id": 1, "amount": 100}, {"id": 2, "amount": 200}] 扁平成单行(orders_0_id, orders_1_amount)往往不如拆成两行直观。这时候就得放弃全局扁平,局部处理数组。
识别出含列表的字段(如 type(value) is list 且非空),对每个子项生成独立记录,并继承父级字段:
def expand_arrays(data, parent_prefix=""):
if isinstance(data, dict):
result = []
arrays = {}
for k, v in data.items():
if isinstance(v, list) and v and isinstance(v[0], dict):
arrays[k] = v
elif isinstance(v, (dict, list)):
result.extend(expand_arrays(v, f"{parent_prefix}{k}_"))
else:
result.append((f"{parent_prefix}{k}", v))
# 对每个数组项,复制其他字段 + 合并当前数组项
if arrays:
base_fields = {k: v for k, v in result if not any(k.startswith(arr_k + "_") for arr_k in arrays)}
for arr_key, arr_items in arrays.items():
for item in arr_items:
item_flat = flatten(item, separator="_")
merged = {**base_fields, **{f"{arr_key}_{k}": v for k, v in item_flat.items()}}
result.append(merged) # 这里返回的是字典列表,需另行聚合
return result
- 这段逻辑不追求通用,而是针对“一个主对象带多个子对象数组”的常见业务场景(如用户+订单、文章+评论)
- 关键点在于:先分离出数组字段,再逐项合并,避免字段名爆炸(比如
orders_0_items_0_name) - 实际使用时,建议只对明确知道是“一对多”关系的字段做此展开,其余仍走扁平化
编码和特殊字符不处理,Excel 打开就是乱码
Python 默认用系统编码写文件,Windows 上常是 gbk,而 Excel(尤其是英文版)默认按 utf-8-sig 解码。结果就是中文全变方块或问号。
写 CSV 时必须显式指定 encoding="utf-8-sig":
with open("output.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(flattened_rows)
-
utf-8-sig比utf-8多一个 BOM 头,Excel 能认出来;纯utf-8在 Excel 里得手动导入并选编码 - 如果字段值含换行符
\n或制表符\t,csv模块会自动加引号,但得确保quoting=csv.QUOTE_MINIMAL(默认就是) - 别信“用 pandas.to_csv() 就不用管编码”——pandas 0.24+ 版本在 Windows 上默认仍是
utf-8,不是utf-8-sig
嵌套越深,字段路径越容易撞名;数组展开越激进,行数膨胀越快。动手前先抽样看三五条原始 JSON,决定哪些字段必须展开、哪些可以扁平、哪些直接丢弃——比写完发现导出 500 列还少数据要省力得多。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











