jq 进阶:select / map / 切片与数组操作
掌握了 .、[]、| 之后,真正让 jq 好用起来的是过滤与变形函数。本文承接 jq 入门, 讲清 select、map、数组切片、路径定位与结构互转, 所有示例都能在 jq 在线工具 对同一份数据复现。
一、示例数据
与站内工作台「填入示例」一致的 store / book 数据,本文所有表达式都对它执行:
{
"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 }
}
}二、select:按条件过滤
select(f) 保留让 f 为真的元素,其余丢弃。条件是普通的 jq 表达式,用当前元素 . 比较:
[.store.book[] | select(.price < 10)]→ 价格低于 10 的两本(Sayings、Moby Dick),仍是完整对象。[.store.book[] | select(.category == "fiction") | .title]→["Sword of Honour", "Moby Dick"]。[.store.book[] | select(.author | test("Waugh")) | .title]→["Sword of Honour"],用正则匹配字符串。
关键点:select 只过滤,不投影。它保留的是满足条件的整个元素;只要其中某个字段,需再接一段管道 | .title。
三、map:映射与变形
map(f) 对数组每个元素应用 f,返回等长的新数组,这也是 jq 里「逐项处理」的标准写法:
.store.book | map(.title)→["Sayings", "Sword of Honour", "Moby Dick"]。.store.book | map(.price)→[8.95, 12.99, 8.99]。.store.book | map(.price) | add→30.93(求和)。.store.book | map({title: .title, price: .price})→ 重塑为只含两个字段的对象数组。
map 内部同样可以放 select:.store.book | map(select(.price < 10)) | map(.title) 与第 2 节过滤后再投影的写法结果一致。
四、数组切片 .[start:end]
切片遵循「含头不含尾」,负下标从末尾数,省略一侧表示取到头 / 到尾:
.store.book[0:2] | .[].title→ 前两本:"Sayings"、"Sword of Honour"。.store.book[1:] | .[].title→ 从第 2 本到末尾:"Sword of Honour"、"Moby Dick"。.store.book[-2:] | .[].title→ 最后两本:"Sword of Honour"、"Moby Dick"。.store.book[0:2] | length→2。
切片本身返回数组,所以后面要接 .[] 展开或 | map(...) 继续处理。
五、paths / getpath:用路径数组定位
当路径不是写死的、而由参数或数据决定时,用「路径数组」而非点号路径:
.store.bicycle | [paths]→[["color"], ["price"]],列出相对当前值的所有叶子路径。getpath(["store", "bicycle", "color"])→"red",等价于.store.bicycle.color但路径可动态拼装。.store.book | map(getpath(["category"]))→ 由每个元素的路径数组批量取值。
六、to_entries / from_entries:对象与数组互转
对象不方便用数组函数处理时,先转成 [{key, value}] 数组,处理完再拼回:
.store.bicycle | to_entries→[{"key":"color","value":"red"}, {"key":"price","value":19.95}]。.store.bicycle | to_entries | from_entries→ 还原成{"color":"red","price":19.95}。.store.bicycle | to_entries | map(.key)→["color", "price"],等价于keys但顺序为书写顺序。.store.bicycle | to_entries | map(.value) | add→ 对全部值求和。
七、实战速查
| 目标 | 表达式 | 结果 |
|---|---|---|
| 低价书标题 | [.store.book[] | select(.price < 10) | .title] | ["Sayings", "Moby Dick"] |
| 书名数组 | .store.book | map(.title) | ["Sayings","Sword of Honour","Moby Dick"] |
| 前两本 | .store.book[0:2] | .[].title | "Sayings"、"Sword of Honour" |
| 后两本 | .store.book[-2:] | .[].title | "Sword of Honour"、"Moby Dick" |
| 价格总和 | .store.book | map(.price) | add | 30.93 |
| 书名→价格映射 | .store.book | map({(.title): .price}) | add | 一个对象:三个书名对应价格 |
| 动态路径取值 | getpath(["store","bicycle","color"]) | "red" |
| 对象转键值数组 | .store.bicycle | to_entries | [{"key":"color","value":"red"}, {"key":"price","value":19.95}] |
八、常见错误
- select 与 map 顺序颠倒。
map(select(f))作用在数组上(保留满足项);select(f) | map(...)在单个元素上没有数组可映射,会报错或结果不符。 - 切片后忘了展开。
.store.book[0:2]是数组,直接看标题要接| .[].title或| map(.title)。 - 切片写成含尾。jq 切片是
[start:end),[0:2]只有 2 个元素(索引 0、1),不是 3 个。 - 对象直接当数组处理。没有
to_entries就map一个对象会报错;先转键值数组再处理。 - getpath 参数写成字符串。它接收路径数组:
getpath(["store","bicycle"]),而不是getpath("store.bicycle")。
动手跑一遍
把本文任一表达式粘进 jq 在线工具,对示例数据即时看到结果,全部在浏览器本地执行(jq 1.8,WASM),数据不上传。
常见问题
select 和 map 应该怎么配合?
记住分工:select 决定「留下哪些元素」,map 决定「把每个元素变成什么样」。常见组合是先筛选再投影,例如 `.store.book | map(select(.price < 10)) | map(.title)` 得到低于 10 元的书名数组;也可以直接在展开后接管道 `.store.book[] | select(.price < 10) | .title`,两者等价。
切片 .[start:end] 和 JSONPath 的切片一样吗?
语义接近:都遵循「含头不含尾」,end 不含。区别是 jq 切片作用在数组/字符串值上(如 `.store.book[0:2]`),JSONPath 切片写在路径里(如 `$.store.book[0:2]`)。负下标在两者中都表示从末尾数,`.[-2:]` 取最后两个。
to_entries 和 from_entries 有什么用?
它们负责在「对象」和「键值对数组」之间来回转换。对象不便用数组函数处理时,先用 `to_entries` 变成 [{key,value}] 数组,用 map / select / sort_by 处理,再用 `from_entries` 拼回对象,这是 jq 里重排、过滤对象键的标准手法。
getpath 和直接写路径有什么区别?
直接写 `.store.bicycle.color` 要求路径在写表达式时就固定;getpath 接收「路径数组」作为参数,可动态拼装:`getpath(["store","bicycle","color"])`。当路径需要由变量或数据决定时(如批量取不同字段),getpath / paths 比硬编码路径灵活得多。
这些示例在哪里可以运行?
打开本站的 jq 在线运行工具,点「填入示例」即得到与本文一致的 store/book 数据,把表达式粘进输入框即可看到相同结果。引擎为官方 jq 1.8,跑在浏览器本地,数据不上传。