拓冰建站拓冰建站
首页 / 资讯中心 / 正文

yq pick 操作符详解:按指定顺序挑选 Map 键与数组元素

yq pick 操作符详解按指定顺序挑选 Map 键与数组元素【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq本文基于 yq 官方文档中pick操作符的说明结合仓库源码实现系统讲解pick的工作原理与实战用法如何按指定列表的顺序从 Map 中筛选键、从数组中按索引筛选元素以及如何结合keys、unique等操作符实现“保留全部键并前置指定键”的高级场景。读完后你可以准确控制输出结果的键序与元素顺序并从源码层面理解 yq 的“保序筛选”机制。pick 操作符概述pick是 yq 的保序筛选操作符它的核心能力是对 Map按指定键列表筛选出对应的键值对返回的 Map 中键的顺序与 pick 列表一致对数组Sequence按指定索引列表筛选出对应元素返回的数组中元素顺序与索引列表一致不存在性容忍列表中不存在于原结构的键或越界的索引会被静默跳过而不是报错。与filter、select这类基于条件表达式的筛选不同pick基于的是显式的键/索引白名单并且天然具备重排能力——你不仅可以筛选还能通过调整 pick 列表的顺序来改变输出的字段顺序。这一特性在处理配置文件重组、API 响应整形、字段对齐等场景时非常实用。从操作符注册信息可以确认其参数与优先级设计见 operation.govar pickOpType operationType{Type: PICK, NumArgs: 1, Precedence: 52, Handler: pickOperator, CheckForPostTraverse: true}NumArgs: 1说明pick恰好接收一个参数一个列表表达式与同族的omitOMIT 操作符注册方式一致二者互为正反操作。从 Map 中按键挑选基础用法给定一个sample.yml文件myMap: cat: meow dog: bark thing: hamster hamster: squeak执行yq .myMap | pick([hamster, cat, goat]) sample.yml输出myMap: hamster: squeak cat: meow这里体现了pick的两个关键行为顺序跟随 pick 列表pick 列表中hamster排在cat之前因此输出中hamster也排在cat之前而不是遵循原文件的键序不存在的键被跳过goat不在myMap中被直接忽略不产生任何报错。注意表达式中使用了|合并赋值.myMap | pick(...)表示把myMap替换为挑选后的结果。如果只需要读取输出整个文档中挑选出的部分可以直接对目标节点调用pick。键匹配是如何实现的pick对 Map 的处理逻辑在 pickMap 函数中func pickMap(original *CandidateNode, indices *CandidateNode) *CandidateNode { filteredContent : make([]*CandidateNode, 0) for index : 0; index len(indices.Content); index index 1 { keyToFind : indices.Content[index] indexInMap : findKeyInMap(original, keyToFind) if indexInMap -1 { clonedKey : original.Content[indexInMap].Copy() clonedValue : original.Content[indexInMap1].Copy() filteredContent append(filteredContent, clonedKey, clonedValue) } } newNode : original.CopyWithoutContent() newNode.AddChildren(filteredContent) return newNode }从源码结构看有三个值得注意的实现细节按 pick 列表顺序遍历外层循环遍历的是indices.Contentpick 列表对列表中的每个键去原 Map 中查找找到才追加。这就是输出顺序由 pick 列表决定的根本原因键值对整体克隆命中后调用Copy()克隆键节点和值节点保证挑选结果与源数据解耦同时保留原始节点上的注释、标签等样式信息浅层键匹配findKeyInMap见 lib.go以步长 2 遍历 Map 的键YAML 节点的Content数组按键、值、键、值交替存放用recursiveNodeEqual比较键。因此 pick 列表中的键应当是标量字符串形式如hamster用于精确匹配键名。高级用法保留全部键并让指定键前置当需求是把某些键提到前面其余键全部保留时可以直接把 pick 列表与当前所有键合并后去重yq .myMap | pick( ([thing] keys) | unique) sample.yml对同样的sample.yml输出为myMap: thing: hamster cat: meow dog: bark hamster: squeak表达式的执行过程keys操作符返回当前 Map 的所有键得到[cat, dog, thing, hamster]原顺序[thing] keys将希望前置的键拼到最前面得到[thing, cat, dog, thing, hamster]unique去重得到[thing, cat, dog, hamster]pick按此列表重排并筛选thing被提到首位其余键保持原有相对顺序。这个拼接 去重 pick的组合模式是 yq 表达式风格中的典型做法pick本身不做集合运算集合语义由、unique、keys等操作符承担pick只负责按最终列表保序提取。从数组中按索引挑选对数组pick的第二个语义是按索引列表筛选元素。给定- cat - leopard - lion执行yq pick([2, 0, 734, -5]) sample.yml输出- lion - cat2取到lion0取到cat输出顺序跟随 pick 列表所以lion在前734越界、-5超出有效负索引范围均被跳过不报错。对应的实现在 pickSequence 中它对 pick 列表中的每一项执行parseInt解析为整数索引indexInArray, err : parseInt(indices.Content[index].Value) if err ! nil { return nil, fmt.Errorf(cannot index array with %v, indices.Content[index].Value) } if indexInArray -1 indexInArray len(original.Content) { filteredContent append(filteredContent, original.Content[indexInArray].Copy()) }可以确认两点实现事实负索引支持判断条件是indexInArray -1 indexInArray len(original.Content)。负数索引除恰好落在有效负位置的情况外与越界正索引一样被跳过即-5这类看起来像越界的值直接略过非整数索引会报错与越界跳过不同如果 pick 列表里出现无法解析为整数的元素parseInt会失败并返回cannot index array with %v错误。这是跳过与报错行为的分界线。操作符调度类型分派与错误处理pickOperator 是pick的入口负责把表达式右值pick 列表求值后对上下文中的每个候选节点做类型分派switch node.Kind { case MappingNode: replacement pickMap(node, indicesToPick) case SequenceNode: replacement, err pickSequence(node, indicesToPick) if err ! nil { return Context{}, err } default: return Context{}, fmt.Errorf(cannot pick indices from type %v (%v), node.Tag, node.GetNicePath()) }从这段代码可以得到几个使用上的结论pick 列表本身是表达式通过d.GetMatchingNodes(context, expressionNode.RHS)求值取第一个匹配节点作为索引列表。因此列表内容可以是任何求值后得到数组的表达式如前面([thing] keys) | unique的例子仅支持 Map 与数组两类节点对字符串、数字等标量使用pick会返回cannot pick indices from type ...错误并附带节点路径GetNicePath便于定位保留节点前导注释replacement.LeadingContent node.LeadingContent一行保证了原节点上的前导注释leading comment在替换后仍然保留。测试用例 operator_pick_test.go 中的 Pick keys from map with comments 场景验证了这一点源文档# abc\nmyMap: ...\n# xyz中的前后注释在 pick 之后原样保留。测试用例中的更多细节operator_pick_test.go 中的场景表覆盖了文档之外的若干行为边界值得了解标签tag保留!things myMap: {cat: meow, dog: bark, thing: hamster, hamster: squeak}执行.myMap | pick([hamster, cat, goat])后!things自定义标签保留在结果节点上!things myMap: {hamster: squeak, cat: meow}这得益于original.CopyWithoutContent()只清空子节点、保留节点元数据标签、样式等的实现方式。Splat 组合用法pick可以与 splat 操作符组合先把子键摊平成独立候选节点再处理yq pick([hamster, cat])[] sample.yml会输出挑选出的两个值每个值作为独立候选squeak meow测试期望值为D0, P[hamster]与D0, P[cat]两个!!str候选说明 splat 后的路径信息仍然指向原 Map 中的键。文档一致性校验测试函数中documentOperatorScenarios(t, pick, pickOperatorScenarios)会校验pkg/yqlib/doc/operators/pick.md中每个场景的文档描述与测试场景一一对应即本文引用的示例输出与仓库当前实现是经过测试保证一致的。与 omit 的关系及适用场景小结从 operation.go 的注册表可以看到PICK与OMIT是成对注册的操作符语义上互为补集操作符语义对不存在项输出顺序pick([a,b])只保留列表中的键/索引跳过跟随 pick 列表omit([a,b])删除列表中的键/索引跳过保留原结构顺序适用场景建议需要重排字段顺序如把配置文件的必填项提到前面用pick配合keys | unique技巧可保留全部键只需要删掉几个键用omit更直接按条件筛选如值满足某表达式pick不适用应使用filter/select等条件类操作符pick 列表可动态求值因为右值是表达式而非字面量可以写pick(keys)、pick( ([x] keys) | unique)这类组合灵活度高。参考资料官方文档pick 操作符说明、文档头部说明核心实现operator_pick.go、findKeyInMap操作符注册operation.go测试用例operator_pick_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),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门