← 中文资源

简体中文版本

把嵌套 JSON 数组摊平成可查询行

用 jq 或 Node 流式把任意深度数组摊平为扁平行,每条记录保留父级上下文与路径来源。

阅读英文原文

目标

嵌套 JSON 适合传输却不适合分析:聚合需要每个叶子一行。一旦先检查数组路径、给每行附上父级上下文、再用“子项总和等于父项总和”的不变量验证,摊平变换就从 spreadsheet 手工折腾变成三行 jq 的机械操作,规模大了再原样翻译成常量内存的 Node 流式生成器。

步骤要点

  1. 先用 paths(type == “array”) 探明文档里所有数组的位置,列出必须摊平的确切路径。
  2. 用 jq 单层展开并把父级字段绑定到每行:. as $o 把订单存进变量,items[] 逐项输出带 orderId 的行。
  3. 处理第二层嵌套时用 (.discounts // [])[]? 保底:无折扣的行目产出零行而不是报 null。
  4. 文件变大后把同样逻辑改写成 Node 异步生成器,readline 逐行解析、嵌套循环产出、常量内存处理 GB 级 JSONL。
  5. 用双重不变量验证:原始与摊平两侧的订单数、行目数、qty 总和完全一致,证明既没丢行也没重复。

排错提示

行里出现不该有的 null 多半是数组路径写错或部分文档结构不同,重新跑 paths 审计并给可选数组加 // [] 默认值;jq -s 吞大文件会爆内存,改为逐行处理 JSONL,或对非行分隔的大文件直接上 Node 流式版本;表格软件导入扁平行乱码是逗号与引号转义问题,改输出 TSV 或 JSONL 让分析工具原生摄取。

适用边界

摊平会把父级上下文复制到每一行,存储量随扇出增长,这是换取可查询性的既定代价;路径审计只有在结构稳定时可靠,上游加一层嵌套后必须重跑。

代码示例

# Where do arrays live?
jq -c '.[0] | paths(type == "array")' orders.json
# ["items"]
# ["items",0,"discounts"]

# Flatten with context, one line per leaf
jq -c '.[] | . as $o
  | .items[] | . as $i
  | (.discounts // [])[]?
  | {orderId: $o.id, sku: $i.sku, code: .code, amount: .amount}' \
  orders.json > flat.jsonl

# Invariant: nothing lost, nothing duplicated
jq -s 'map(.items | map(.qty) | add) | add' orders.json   # 13,014
jq -s 'map(.qty) | add' flat.jsonl                        # 13,014