read_json() 不展平嵌套结构,仅做基础类型转换;json_normalize() 才专用于展平,需配合使用并注意字段提取、点号列名处理及性能优化。

read_json() 读出来还是嵌套字典,根本没展平
这是因为 read_json() 默认只做 JSON 字符串到 Python 对象的转换,不处理嵌套结构——它把 JSON 数组转成 list,对象转成 dict,原样塞进 DataFrame 的单元格里。你看到的“嵌套”,其实是某个列里存着 {'user': {'id': 123, 'name': 'Alice'}, 'score': 95} 这种 dict,不是 DataFrame 的多级列。
常见错误现象:df['data'].apply(type) 返回 <class 'dict'>;用 df['data']['user'] 报 KeyError;想直接取 df['data.user.id'] 不生效。
- 如果原始 JSON 是数组(如
[{"a": 1, "b": {"c": 2}}]),read_json()能生成 DataFrame,但b列仍是 dict 类型 - 如果原始 JSON 是单个对象(如
{"results": [...]}),read_json()会生成 1 行 DataFrame,results列存整个 list —— 这时候连行都没展开 - 别指望
orient参数(如'records'或'columns')能自动展平嵌套字段;它只管顶层结构怎么映射成行/列
json_normalize() 才是专治嵌套的工具
json_normalize() 的设计目标就是把嵌套 dict/list 拉成扁平列,比如把 {'user': {'id': 123, 'profile': {'city': 'Beijing'}}} 变成三列:user.id、user.profile.city、甚至可选的 user 原始 dict(用 max_level=0)。
使用场景:API 返回的 JSON 响应体里有 data 字段包着数组,或每条记录里有 metadata、address 等子对象。
- 最简用法:
json_normalize(data),其中data是 list of dict 或单个 dict - 从深层字段提取数组:用
record_path指定路径,比如json_normalize(data, record_path=['results', 'items']) - 保留父级字段:用
meta传入要提升上来的字段名列表,如meta=['id', 'timestamp', ['user', 'name']] - 注意
errors='ignore'和errors='raise'的区别:字段缺失时前者跳过,后者报错
read_json() + json_normalize() 组合才是实战常态
真实数据往往不是纯数组 JSON 文件,而是带包装层的响应体(如 {"status": "ok", "data": [{"id": 1}, {"id": 2}]})。这时候不能只靠 read_json(),也不能直接把整个文件内容喂给 json_normalize() —— 因为 json_normalize() 接收的是 Python 对象,不是文件路径。
典型流程是:先用 read_json() 加载,再对结果中具体的嵌套字段调用 json_normalize()。
- 读取后取字段:
raw = pd.read_json('api.json'); df = json_normalize(raw['data']) - 如果
raw['data']是字符串而非 dict/list,先json.loads()解析(常见于字段被双重序列化) - 避免重复解析:不要写
json_normalize(pd.read_json('x.json').to_dict('records')),这会把已解析的对象又转回 dict 再展平,多余且易错 - 性能影响:
json_normalize()在内部做了递归遍历,嵌套层级深、数据量大时明显变慢;可先用sample(1000)测试结构再全量处理
展平后字段名含点号,后续操作容易出问题
json_normalize() 默认生成的列名是 a.b.c 这种带点的,Pandas 允许,但很多下游操作不友好:比如 df.a.b.c 会报错(属性链不支持点号),df['a.b.c'] 又太啰嗦,SQL 导出、数据库写入、某些可视化工具也会拒绝点号列名。
这不是 bug,是设计使然——它靠点号表达嵌套路径。但生产环境通常得改掉。
- 快速清理:用
df.columns = df.columns.str.replace('.', '_')(简单粗暴) - 更稳妥:在
json_normalize()中加sep='_'参数,从源头控制分隔符 - 注意
meta字段也会被加前缀,比如meta=[['user', 'info']]会生成user_info_id,不是user.info.id - 如果原始 JSON 里就有字段名含点(极少见),
sep改了也没用,得预处理 key —— 这种情况基本说明数据源不规范,优先推动上游修复
嵌套 JSON 展平从来不是“一键解决”的事,关键在判断哪一层该展开、哪些字段要提升、点号列名要不要动——这些决定比函数调用本身更重要。










