pandas.json_normalize() 是首选,因为它能递归展开多层嵌套JSON(如"user.profile.age"),而pd.read_json()仅支持扁平或单层结构,遇嵌套即存为object类型导致后续计算失败。

为什么 pandas.json_normalize() 是首选,而不是 pd.read_json()
pd.read_json() 只能处理扁平结构或单层嵌套的 JSON;一旦遇到多层嵌套(比如 "user": {"name": "Alice", "profile": {"age": 30, "city": "Beijing"}}),它会把整个子对象塞进一个 object 类型的 Series 里,后续无法直接做数值计算或分组。而 pandas.json_normalize() 专为“打平”设计,能递归展开字典、列表混合结构,并支持路径式字段提取。
常见错误现象:TypeError: unhashable type: 'dict' 或 AttributeError: 'str' object has no attribute 'keys',往往就是误用 read_json 后试图对嵌套列直接调用 .str 或 .explode() 导致的。
- 使用场景:API 返回的嵌套响应、MongoDB 导出 JSON、日志中带结构化 payload 的字段
- 关键参数:
record_path指定要展开的嵌套列表路径(如["orders", "items"]);meta指定需保留的外层字段(如["user.id", "timestamp"]) - 注意:
record_path必须指向一个list,不是dict;若目标是字典内字段,直接用sep和点号路径即可
如何处理含列表的嵌套字段(比如订单里的多个商品)
当 JSON 中某字段是列表(如 "tags": ["python", "pandas"] 或 "items": [{"id": 1}, {"id": 2}]),直接用 json_normalize 默认不会展开该列表——它只会展开最外层的记录级列表。需要先用 .explode() 拆开,再用 json_normalize 处理子结构。
示例:原始数据中 data = [{"id": 100, "items": [{"sku": "A", "qty": 2}, {"sku": "B", "qty": 1}]}]
import pandas as pd
df = pd.json_normalize(data)
df_exploded = df.explode('items').reset_index(drop=True)
df_flat = pd.json_normalize(
df_exploded.to_dict('records'),
sep='_'
)
- 必须用
to_dict('records')转成字典列表,否则json_normalize会报错ValueError: data must be a list or dictionary -
sep='_'避免字段名含点号(如items.sku)导致后续写 SQL 或导出 Excel 出问题 - 如果
items列有空值或非列表类型,explode会跳过或报错,建议先df['items'] = df['items'].apply(lambda x: x if isinstance(x, list) else [])
如何避免字段名冲突和重复列
嵌套结构中常出现同名字段(如外层 id 和内层 items.id),json_normalize 默认用 sep 连接路径生成新列名,但若未指定 meta 或路径不明确,可能产生重复列或覆盖。
典型问题:json_normalize(data, meta=["id"], record_path="items") 会导致外层 id 和内层 items.id 都叫 id,后者覆盖前者。
- 解决方法:显式重命名
meta字段,例如meta=[["id", "order_id"]],这样外层id就变成order_id - 检查是否有多余字段:展开后用
df.columns[df.columns.str.contains('unnamed', case=False)]找出 pandas 自动生成的冗余列(常因 JSON 字段缺失导致) - 若字段值本身是 JSON 字符串(如数据库里存的 text 字段),需先
df['payload'] = df['payload'].apply(json.loads),再传给json_normalize,否则会当成普通字符串处理
性能敏感时要注意什么
对万级以上的嵌套 JSON 记录,json_normalize + explode 组合可能比纯 Python 循环还慢,尤其当嵌套深度 >3 或列表长度差异极大时。
- 提速技巧:用
max_level参数限制展开深度(如max_level=2只展开两层,避免无意义的深层遍历) - 内存警告:
explode会让行数暴增,处理前先df['items'].str.len().describe()看平均嵌套数量,预估膨胀后规模 - 替代方案:对于超大 JSON,考虑用
dask.dataframe.read_json分块读取,或用jq命令行工具预处理(cat data.json | jq '[.[] | {id: .id, sku: .items[].sku}]')再加载
最易被忽略的一点:json_normalize 不会自动推断数字类型,所有字段默认是 object,后续做聚合前务必用 df.select_dtypes('object').apply(pd.to_numeric, errors='ignore') 批量转换,否则 sum() 会静默失败或返回字符串拼接结果。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











