yq 的 map 与 map_values 操作符:数组与对象批量变换实战指南
【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq
map与map_values是 yq(portable command-line YAML/JSON/XML/CSV/TOML/HCL/properties processor)中用于批量变换集合元素的一对核心操作符:map作用于数组的每个元素,map_values作用于对象的每个值。本文以 pkg/yqlib/doc/operators/map.md 为骨架,结合 yq 源码(pkg/yqlib/operator_map.go、pkg/yqlib/operation.go、pkg/yqlib/operator_map_test.go等)深入讲解二者的语法、用法、底层实现与测试验证,读完即可在 YAML/JSON 处理脚本中熟练运用批量映射能力。
基本语义
在 yq 中:
map(expr):对数组中的每一个元素执行表达式expr,返回一个新数组,元素数量与输入保持一致;map_values(expr):对对象(map)中的每一个值执行表达式expr,键保持不变,仅替换值。
二者的共同点是:映射表达式内部通过.引用"当前正在处理的元素/值",这与 jq 的map语义一致,便于从 jq 迁移的用户快速上手。
map:数组元素映射
官方示例
假设存在sample.yml文件,内容为:
- 1 - 2 - 3执行:
yq 'map(. + 1)' sample.yml输出:
- 2 - 3 - 4map(. + 1)的含义是:把数组中的每个元素依次取出来,代入.,计算. + 1,再将所有计算结果组装成一个新数组。
映射空数组
当输入数组为空时,map不会报错,也不会产生任何元素。源码pkg/yqlib/operator_map.go中明确处理了这一分支:当splatted.MatchingNodes.Len() == 0时,直接复制原始候选节点(candidate.Copy())并放入结果集。对应测试(pkg/yqlib/operator_map_test.go中 "mapping against an empty array should do nothing")验证:
yq '[] | map(. + 42)' sample.yml输出为空数组[],不会抛异常。
映射结果再展开
map的输出本身是一个数组,可以在其后追加[]将其展开为流式节点。例如测试用例:
yq 'map(. + 1)[]' sample.yml输入[1, 2]时输出两个独立节点:
2 3这在后续需要逐元素继续管道处理时非常实用。
对嵌套数组映射
map可以结合路径选择作用于嵌套结构。测试用例中:
yq '.[] |= map(3 + .)' sample.yml输入[[], [5]]时输出[[], [8]]:外层.[]遍历每一个子数组,map(3 + .)对每个子数组内部元素做变换,空子数组保持不变。
map_values:对象值映射
官方示例
假设sample.yml内容为:
a: 1 b: 2 c: 3执行:
yq 'map_values(. + 1)' sample.yml输出:
a: 2 b: 3 c: 4键a、b、c全部保留,值1/2/3分别被替换为2/3/4。
对空对象映射
与空数组类似,对空对象{}执行map_values同样安全无副作用,测试用例验证yq 'map_values(3 + .)'作用于{}时输出仍为{},不会产生错误。
结果展开为节点流
测试用例 "map values splat" 展示了map_values(. + 1)[]的用法:输入{a: 1, b: 2}时,输出为两条独立记录(每条带各自路径):
a: 2 b: 3map_values配合[]展开后,每个键值对成为独立节点,便于继续使用.key、to_entries等操作符做二次处理。
源码级实现解析
词法识别:map 与 map_values 的标记
在词法层,pkg/yqlib/lexer_participle.go中定义了两个 token:
simpleOp("map", mapOpType):将关键字map解析为mapOpType;{"MapValues", 'map_?values', opToken(mapValuesOpType), 0}:使用正则map_?values匹配map_values或mapvalues两种写法(即下划线可选),解析为mapValuesOpType。
操作符注册:参数个数与优先级
pkg/yqlib/operation.go中注册了这两个操作符:
var mapOpType = &operationType{Type: "MAP", NumArgs: 1, Precedence: 52, Handler: mapOperator, CheckForPostTraverse: true} var mapValuesOpType = &operationType{Type: "MAP_VALUES", NumArgs: 1, Precedence: 52, Handler: mapValuesOperator, CheckForPostTraverse: true}二者均为单参数操作符(NumArgs: 1,即括号内的映射表达式),优先级为 52(与filter、pick、omit同级),并开启CheckForPostTraverse——这就是map(...)[]之后能直接展开遍历的原因。
mapOperator:求值后收集
核心实现位于pkg/yqlib/operator_map.go的mapOperator函数,流程为:
- 遍历所有匹配节点(
context.MatchingNodes); - 对每个候选节点调用
splat(见pkg/yqlib/operator_traverse_path.go中的splat函数)将其展开为元素序列; - 若展开结果为空,直接复制原节点(保证空数组/空对象安全);
- 否则对展开结果执行表达式树的 RHS(即括号内的映射表达式);
- 用
collectTogether(pkg/yqlib/operator_collect.go)把所有结果收集进一个新的!!seq节点,并继承原候选节点的样式(collected.Style = candidate.Style),从而保持输出风格一致。
mapValuesOperator:原地更新
mapValuesOperator的实现则走了另一条路径:对每个候选节点 splat 之后,构造一个assignUpdateExp(OperationType: assignOpType, UpdateAssign: true),把 RHS 表达式作为更新目标,调用assignUpdateOperator原地更新各节点的值。这正是map_values能"保留键、只改值"的底层原因——它本质上是一次批量赋值更新,而非重新组装集合。
相关操作符与组合用法
map 与.[] |的区别
map(expr)可以视为对"当前集合内每个元素应用表达式"的语法糖,等价于先将数组展开、逐个变换再重新收集。它与.[] |= expr的差异在于:map显式返回一个新数组,且对空数组有专门的兜底处理;而.[] |=是路径赋值语义。测试用例中.[] |= map(3 + .)也展示了二者可以嵌套配合。
map_values 与 entries 系列操作符
map_values只关注值;若需要同时变换键和值,可与to_entries/from_entries(pkg/yqlib/operator_entries.go)配合:先用to_entries把对象转成{key, value}数组,用map自由变换后再from_entries还原为对象。这也是社区常用的"键值同时处理"模式:
yq 'to_entries | map({key: .key, value: (.value * 2)}) | from_entries' sample.yml组合管道示例
映射表达式可以是任意 yq 表达式,包括函数调用、条件判断与嵌套映射:
# 对数组中的数字取绝对值 yq 'map(. * -1)' sample.yml # 根据条件保留性变换对象值 yq 'map_values(select(. > 1) | . + 100)' sample.yml # 数组内嵌套对象字段批量修改 yq 'map(.price = .price * 0.9)' sample.yml测试验证
pkg/yqlib/operator_map_test.go通过mapOperatorScenarios表驱动测试覆盖了本操作符的核心行为,其中包括:
Map array:map(. + 1)对[1,2,3]输出[2, 3, 4];Map object values:map_values(. + 1)对{a: 1, b: 2, c: 3}输出{a: 2, b: 3, c: 4};- 空数组、空对象场景的幂等安全行为;
- 多文档(
document+document2)下每个文档独立映射; map(...)[]与map_values(...)[]的展开输出。
这些场景与文档示例一一对应,可作为理解map/map_values行为边界的可靠参考。
总结
map与map_values是 yq 中处理集合数据的高频操作符:前者面向数组逐元素映射,后者面向对象逐值映射,二者语法一致(括号内用.引用当前元素),且对空集合安全、支持管道展开。通过阅读 pkg/yqlib/operator_map.go 可以进一步理解其"求值-收集"与"批量赋值更新"两条实现路径,结合 pkg/yqlib/operator_map_test.go 的测试场景即可在真实脚本中放心使用。
【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考