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) | add30.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,跑在浏览器本地,数据不上传。

相关工具