用 jq 清洗订单 JSON:先隔离异常,再统计已支付金额

用 jq 清洗订单 JSON:先隔离异常,再统计已支付金额

接口导出的 JSON 能解析,不代表里面的金额就能直接求和。退款可能仍带着原订单金额,缺失值可能是 null,数字也可能被输出成字符串。如果一开始就把它们统一转成数字、空值补零,报表不一定报错,却已经换了业务口径。

最近查看 API 更新时,我注意到 Google 10 月 26 日调整了新项目的请求存储默认值;现有项目保留设置。1 这只是数据留存相关的选题线索,不是 jq 或结构化输出的新功能。对 AI/API 工作流而言,保存下来以后如何检查数据,同样值得做一个小而可复现的实验。

本次在 Linux 的 jq-1.7 上实际执行。下面七条订单完全由我构造,不是真实客户数据,也不是某个模型的实际回复。目的只有一个:用 jq 保留异常理由,再按明确规则统计有效的已支付金额。

先把统计口径写出来

这里的金额单位是"分",只接受非负整数。仅 status 为 paid 的记录进入汇总。退款和其他状态单独保留,不参与计算;记录不是对象、编号不是字符串,或已支付记录的金额不合格,则放进 bad。

保存为 orders.json:

json 复制代码
{
  "orders": [
    {"id":"A100","status":"paid","amount_cents":1200},
    {"id":"A101","status":"paid","amount_cents":800},
    {"id":"A102","status":"refunded","amount_cents":500},
    {"id":"A103","status":"paid","amount_cents":null},
    {"id":"A104","status":"paid","amount_cents":"700"},
    {"id":"A105","status":"paid","amount_cents":-300},
    "not-an-order"
  ]
}

先试一条看似省事的命令:

bash 复制代码
jq '[.orders[] | objects | (.amount_cents | tonumber? // 0)] | add' orders.json
# 实际输出:2900

它滤掉了非对象,却把退款的 500 分算了进去,把字符串金额转成了 700,还让负数抵减总额。null 被补成零,看不出原始缺失。2900 是这条命令的真实结果,但不是本例规则下的正确支付金额。问题不是加法算错,而是筛选和修复策略未经定义。

分类以后再汇总

把下面保存为 report.jq。我把分类独立成函数,这样异常保留在哪里、为什么不计入,都能从输出追溯。

jq 复制代码
def classify:
  if type != "object" then
    {bucket:"bad", reason:"not_object", raw:.}
  elif (.id | type) != "string" then
    {bucket:"bad", reason:"id_not_string", raw:.}
  elif .status == "refunded" then
    {bucket:"ignored", id:.id, reason:"refunded"}
  elif .status != "paid" then
    {bucket:"ignored", id:.id, reason:"not_paid"}
  elif (.amount_cents | type) != "number" then
    {bucket:"bad", id:.id, reason:"amount_not_number"}
  elif .amount_cents < 0 or (.amount_cents | floor) != .amount_cents then
    {bucket:"bad", id:.id, reason:"amount_not_nonnegative_integer"}
  else
    {bucket:"accepted", id:.id, amount_cents:.amount_cents}
  end;

if type != "object" then error("root must be object")
elif (.orders | type) != "array" then error("orders must be array")
else
  [.orders[] | classify] as $rows
  | [$rows[] | select(.bucket == "accepted")] as $ok
  | {
      accepted_count: ($ok | length),
      paid_total_cents: ([$ok[].amount_cents] | add // 0),
      accepted: $ok,
      ignored: [$rows[] | select(.bucket == "ignored")],
      bad: [$rows[] | select(.bucket == "bad")]
    }
end

先检查根结构,再检查每条记录,是这里刻意安排的顺序。根节点不合格会直接 error,而单条坏数据不会把整份报告中断。不要把两类失败都吞掉,否则上游把 orders 改成对象,也可能被当作"今天没有订单"。

状态筛选在金额校验之前。因此退款订单只留下编号和退款原因,本脚本不检查退款金额是否完整 ;非 paid 状态也统一标为 not_paid。如果要审计未知状态或核对退款流水,应另加规则,而不能说这个输出已经验证了全部字段。

实际运行得到什么

bash 复制代码
jq --version
# jq-1.7
jq -f report.jq orders.json > report.json
jq '{accepted_count, paid_total_cents,
     ignored_count:(.ignored|length), bad_count:(.bad|length)}' report.json

最后一条命令的实际输出是:

json 复制代码
{
  "accepted_count": 2,
  "paid_total_cents": 2000,
  "ignored_count": 1,
  "bad_count": 4
}

图中内容来自本次保存的 stdout 和后续筛选命令,只做排版,不是终端界面截图。两条有效订单合计 2000 分,也就是 20 元。四条坏记录没有消失:金额缺失和字符串类型分别能定位到 A103、A104,负数对应 A105,非对象记录保留在 raw 中。这样修数据时不必重新猜测问题来自哪条输入。

空列表与错误结构不能混为一谈

我另外实际执行了六组边界输入:空 orders、零金额支付、待支付状态、小数金额、根数组、orders:null。前四组命令退出码都是 0;空列表总额为 0,零金额可接受,待支付单进入 ignored,1.5 分进入 bad。最后两组退出码均为 5,stderr 分别提示根必须是对象、orders 必须是数组。

jq 手册说明,空数组执行 add 返回 null。2 所以本例在已通过筛选的金额列表后用 add // 0:这是把"没有有效支付记录"表达成零,不是给未知金额随手补零。两处看起来一样的 // 0,放置位置不同,含义完全不同。

这份脚本适合什么,不适合什么

它适合小型接口导出或测试夹具的快速检查,不是财务结算系统。脚本不去重订单编号,不验证编号是否为空,也不处理多币种、部分退款和超大整数的精度边界。金额用整数分能避开本例的小数金额输入,但不能据此保证任意规模的金额计算精确。

异常 raw 也可能包含敏感字段;真实项目里应按需要删减字段后再写日志。本例全部是合成数据,所以保留原值便于复现。大文件则还要考虑内存:这里构造了完整分类数组,不是流式处理。

使用 GPT 辅助写过滤器时,我更愿意先给出这些输入和规则,再检查脚本输出,而不是把一条能运行的命令当作可靠报表。gpt68.com 是第三方 AI 会员服务平台,并非模型厂商官网或授权合作方;会员服务与本例的数据正确性没有等价关系。

**AI 辅助说明:**本文整理过程使用 AI 辅助,规则、合成输入及上述结果已在本地 jq 实际运行核对;没有调用模型生成订单或虚构模型答复。

参考资料

相关推荐
qq_340040883 小时前
idea2021版,vue项目的package.json不展示scripts的绿色箭头
javascript·vue.js·json
骑着蜗牛撵大象3275 小时前
大模型 SDK 编译就崩?用 CMakeLists 把 httplib、JSON 与 OpenSSL 装配成可复用的 ChatServer
android·java·c++·http·json·cmake·大模型sdk
rich_Chan.1 天前
阿里云百炼联网搜索教程:web_search 工具开启方法、web_extractor 网页抓取与 json 检索轨迹
前端·阿里云·json
SEO_juper2 天前
Node.js 重定向链检测工具:301 跳了几跳、有没有成环,一次看清
xml·开发语言·python·node.js·json·bash
代码丰2 天前
跨系统传输大 JSON:直接压缩,还是把数据放进 Redis?
redis·后端·json
司曜珩2 天前
构建AI友好型网站:用JSON-LD给企业官网上“户口”的实战指南
人工智能·json
Jmyd01232 天前
智慧广交会常态化运营下,企业如何用3D展厅把展会流量变成365天询盘
json
aluluka3 天前
《elisp 每日一练》——Process Sentinel 与 json集成
json·emacs·lisp·elisp