PAYATHON 2026

Python 如何用 pandas 展平嵌套 JSON,使每个 outcome.id 对应一行?

数据阿明

结论

将 record_path 设为 "reports",并通过 meta=["_id"] 保留顶层标识。pd.json_normalize 会把每条 report 中嵌套的 outcome 和 status 字典展开成列,不必把 outcome 放入 record_path。

示例中每条 report 都包含一个 outcome,展开 reports 后,两个 outcome.id 各占一行。下面的代码将顶层 _id 命名为 report_id;如果期望 report_id 来自其他字段,需要相应调整。

展平 JSON 并调整列名

假设 data 已经是题目中的 Python 字典。如果输入是 JSON 字符串,请先用 json.loads 解析。

import pandas as pd

# data 为题目中的 JSON 解析得到的字典
 df_reports = pd.json_normalize(
    data,
    record_path="reports",
    meta=["_id"],
    sep=".",
)

上面的赋值语句多了前导缩进。下面的简写示例也有同样的问题,实际使用时请采用后面的完整代码。

import pandas as pd

# 每个 reports 元素生成一行,并保留顶层 _id
 df_reports = pd.json_normalize(data, record_path="reports", meta=["_id"])

完整处理代码如下:

import pandas as pd

# data 为题目中的 JSON 解析得到的字典
reports = pd.json_normalize(
    data,
    record_path="reports",
    meta=["_id"],
    sep=".",
)

# 将顶层标识命名为 report_id
reports = reports.rename(columns={"_id": "report_id"})

# 自动生成的 outcome.status.* 列改为期望的 status.*
prefix = "outcome.status."
reports = reports.rename(columns={
    column: "status." + column[len(prefix):]
    for column in reports.columns
    if column.startswith(prefix)
})

# 调整列顺序,其余字段继续保留
first_columns = [
    "report_id",
    "outcome.id",
    "body",
    "outcome.comments",
]
remaining_columns = [
    column for column in reports.columns
    if column not in first_columns
]
df_reports = reports[first_columns + remaining_columns]

得到的列包括:

report_id
outcome.id
body
outcome.comments
status.failed.both
status.failed.human
status.failed.auto
status.failed.total
status.open.both
status.open.human
status.open.auto
status.open.total
status.passed.both
status.passed.human
status.passed.auto
status.passed.total
status.code._input
status.code._output
status.code.canceled
status.total
type

原始 type 字段也会保留。如果不需要,可以执行:

df_reports = df_reports.drop(columns=["type"])

为什么原来的参数不合适?

record_path 用于指定生成多条记录的列表。题目中 reports 是列表,每条 report 内的 outcome 是字典,因此应在 reports 这一层确定行数。

record_path=["reports", "outcome"]

这个路径最终指向字典,不符合这里需要的记录列表结构。json_normalize 可以递归展开 outcome.status 内部的字典,无需通过 record_path 继续深入。

meta 用于携带记录列表之外的上下文字段。_id 位于 reports 外部,所以放入 meta;body、outcome.id、outcome.comments 和 outcome.status 都在 report 内部,会自动进入结果,无需重复声明。

原代码还将 comments 写成了 comment,示例末尾也缺少结束函数调用的右括号 )。

如何确认每个 outcome 的状态都在同一行?

可以选取几个字段查看结果:

print(df_reports[[
    "report_id",
    "outcome.id",
    "status.passed.auto",
    "status.total",
]].to_string(index=False))

按题目中的数据,对应值为:

 report_id outcome.id  status.passed.auto  status.total
     12345          1                   1             1
     12345          2                   1             1

outcome.id 在原始 JSON 中是字符串,展平后仍保留这个类型。outcome.comments 是列表,也会保留在单元格中;不需要调用 explode,否则可能改变行数。

json_normalize 按 reports 元素生成行,不会按 outcome.id 自动去重或合并。如果实际数据中同一个 ID 出现多次,需要先明确这些记录应如何合并;展平操作不能保证 ID 唯一。

备注:内容仅供参考。