jq 入门:从格式化到复杂过滤
jq 是命令行里处理 JSON 的常用工具,用管道把「取值、过滤、分组、排序、重塑」串成一条表达式, 比手写脚本省事。本文用一份固定的 store / book 示例数据,把 .、[]、| 三条基本功与常用函数讲清楚,示例都能在站内 jq 在线运行工具 里复现(官方 jq 1.8,浏览器内 WASM 执行,无需安装)。
一、先准备一份示例数据
下面这份数据和站内工作台内置的「填入示例」一致,本文所有表达式都可以直接对 它 执行, 结果与文中一致:
{
"store": {
"book": [
{ "category": "reference", "title": "Sayings", "author": "Nigel Rees", "price": 8.95 },
{ "category": "fiction", "title": "Sword of Honour", "author": "Evelyn Waugh", "price": 12.99 },
{ "category": "fiction", "title": "Moby Dick", "author": "Herman Melville", "price": 8.99 }
],
"bicycle": { "color": "red", "price": 19.95 }
}
}二、jq 的三条基本功
每条 jq 表达式描述的都是「对当前输入做什么」,把下面三种动作串起来,就能写出大部分查询:
- 取字段:
.key。.代表「当前输入」本身,.store取 store 字段, 再.book继续下钻;字段名带特殊符号时用.["weird-key"]。 - 展开数组:
[]。.store.book是整个数组(一个值);.store.book[]会把数组拆开,把三个元素依次输出(三个值)。 - 管道:
|。 把左边的输出当作右边的输入:.store.book | length即「先取 book,再求长度」。表达式从左到右读,就是数据的流动方向。
用方括号把一串管道包起来,可把「多行输出」重新收集成一个数组:[.store.book[] | .title] 得到 ["Sayings","Sword of Honour","Moby Dick"],而不加方括号时是三个独立输出。
三、jq 的常用写法
下表覆盖入门最常用的一批写法,均可对示例数据直接运行:
| 写法 | 含义 | 示例 |
|---|---|---|
.key | 取对象字段 | .store.bicycle.color |
[i] | 数组下标(负数从末尾数) | .store.book[0].title |
[] | 展开数组为多个输出 | .store.book[] |
| | 管道,左值作为右值输入 | .store.book | length |
map(f) | 对数组每项应用 f,返回数组 | map(.price) |
select(f) | 保留满足条件的项(只过滤,不投影) | select(.price < 10) |
[ ... ] | 把多行输出收集成数组 | [.store.book[] | .title] |
更进阶的内建函数如 group_bysort_byaddtest// 见下文实战与速查表。
四、实战:对示例数据逐条运行
下表每一行都可以在上面的示例数据上复现(结果为实际输出值):
| 目标 | 表达式 | 结果 |
|---|---|---|
| 取单个字段 | .store.bicycle.color | "red" |
| 第一本书的标题 | .store.book[0].title | "Sayings" |
| 最后一本书 | .store.book[-1].title | "Moby Dick" |
| 展开数组取所有标题 | .store.book[] | .title | 三行输出:"Sayings"、"Sword of Honour"、"Moby Dick" |
| 所有价格数组 | .store.book | map(.price) | [8.95, 12.99, 8.99] |
| 书的数量 | .store.book | length | 3 |
| 价格低于 10 的书(完整对象) | [.store.book[] | select(.price < 10)] | 两条 book 对象:Sayings、Moby Dick |
| 价格低于 10 的标题 | [.store.book[] | select(.price < 10) | .title] | ["Sayings", "Moby Dick"] |
| 按价格升序取标题 | .store.book | sort_by(.price) | .[].title | "Sayings"、"Moby Dick"、"Sword of Honour" |
| 平均价格 | .store.book | map(.price) | add / length | 10.31 |
| store 的键(已排序) | .store | keys | ["bicycle", "book"] |
| 字段缺失用默认值 | .store.car // {color: "unknown"} | {color: "unknown"} |
五、常见错误与排查
- select 只过滤、不投影。
select(.price < 10)输出的是整个元素;只要标题需再接一段管道:select(.price < 10) | .title。 - 想要数组却忘了方括号。
.store.book[] | .title是三个独立输出(三行裸串);套上外层方括号[.store.book[] | .title]才得到一个数组。 - 把 JSONPath 的 $ 或 @ 带进 jq。 jq 里的「当前输入」是
., 不是$或@;过滤要写select(.price < 10), 容器与字段都点开头,例如.store.book。 - 对字符串做算术。 对非数字值做
+-或tonumber会触发运行时错误, 结果区会原样显示 jq 报错并归类为「jq 运行时错误」。 - 误以为 keys 保持原顺序。
keys返回的是排序后的键名(示例里是["bicycle","book"]), 不是数据里的书写顺序;需要原顺序时可结合to_entries。
排查建议:先用 . 看整体结构,再用 keys 或 .. 确认层级,然后逐步加管道。 对 null 取字段在 jq 1.8 中不报错(如 {} | .a.b 得 null), 取不到数据时优先怀疑层级,而不是以为表达式崩了。
六、速查表
工作台内置的速查卡按「基础 / 遍历与映射 / 过滤与分组 / 对象与默认值」四组给出可运行示例,点击即填入表达式,可对照下表速记:
| 分类 | 表达式 | 说明 |
|---|---|---|
| 基础 | .store.book[-1].title | 负数取最后一个 |
| 遍历映射 | .store.book | map(.price) | 映射为字段数组 |
| 重塑结构 | [.store.book[] | {title, price}] | 重塑为新对象数组 |
| 过滤 | [.store.book[] | select(.price < 10)] | 条件过滤成数组 |
| 分组 | .store.book | group_by(.category) | 按字段分组(先排序) |
| 排序 | .store.book | sort_by(.price) | 按字段升序排序 |
| 正则匹配 | [.store.book[] | select(.author | test("Waugh"))] | 按正则筛选元素 |
| 拼接字符串 | .store.book | map(.title) | join(", ") | 数组拼接为字符串 |
| 默认值 | .store.car // {color: "unknown"} | 空值兜底(// 操作符) |
现在就试一条 jq 表达式
粘贴 JSON 即可用官方 jq 1.8 实时过滤、映射、分组与排序,全部在浏览器本地完成,数据不会上传。
常见问题
jq 和 JSONPath 有什么区别?该先学哪个?
JSONPath 只做「路径定位」,语法接近 XPath,一行表达式选出节点,上手快;jq 是完整的数据处理语言,有管道、map / select / group_by、排序和对象重塑,能力更强但语法更多。建议先用 JSONPath 快速取值,需要过滤、分组、改写结构时再切 jq;本站两个引擎在同一页面切换,JSON 数据保持不变。
.store.book[] 和 .store.book 有什么区别?
`.store.book` 得到的是「book 这个数组」本身(一个值);在后面加 [] 变成 `.store.book[]` 会「展开」数组,把三个元素依次输出(三个值)。想看数组长度用 `.store.book | length`,想逐个处理元素用 `.store.book[]`,这就是 `[]` 的关键作用。
select 过滤后为什么输出的是整个元素,不是某个字段?
select 只负责过滤、不做投影:`[.store.book[] | select(.price < 10)]` 输出的是完整的 book 对象数组。只要标题就再接一段管道 `| .title`,即 `[.store.book[] | select(.price < 10) | .title]`。这是 jq 管道从左到右阅读最直观的体现。
为什么 select(.price < 10) 里不能用 @?
`@` 是 JSONPath 里代表当前节点的写法,jq 不用 @:jq 中「当前输入」就是 `.`,所以过滤写成 `select(.price < 10)`。把 JSONPath 表达式直接粘到 jq 引擎里最常见的报错,就是误用了 $ 或 @。
在线运行 jq 会上传我的数据吗?第一次为什么要等一下?
不会上传。jq 引擎跑在浏览器本地的 Web Worker 中,站点没有后端服务,可打开开发者工具 Network 面板自证。第一次执行 jq 查询时会按需下载并编译约 0.9MB 的 WASM 引擎(官方 jq 1.8.2,仅一次,之后走缓存),所以首查会稍慢,之后即时执行。