yq group_by 操作符详解:按表达式对数组元素分组

📅 发布时间:2026/9/14 4:06:39
yq group_by 操作符详解:按表达式对数组元素分组
yq group_by 操作符详解按表达式对数组元素分组【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yqgroup_by是 yq 中用于按指定表达式对数组内元素进行分组的核心操作符它将数组中具有相同表达式求值结果的元素聚拢到同一个子数组适用于数据归类、去重统计、报表聚合等场景。读完本文你将掌握group_by的完整语法、空值处理规则、分组顺序语义并通过源码级剖析理解其底层实现原理与测试验证方式。一、group_by 是什么group_by操作符用于按一个表达式对数组中的条目进行分组group items in an array by an expression。它的使用形式为yq group_by(表达式) 文件输入必须是一个数组Sequencegroup_by会对数组中的每个元素求值表达式将求值结果相同的元素归入同一组输出是一个新的数组其每个元素都是一个子数组代表一个分组分组的顺序遵循首次出现顺序哪个 key 第一次出现其分组就排在前面。在 yq 操作符文档中group_by的官方说明位于 pkg/yqlib/doc/operators/group-by.md其行为语义与 jq 的group_by保持一致。二、基础用法按字段分组2.1 准备数据假设有一个sample.yml文件内容为三个对象组成的数组每个对象包含foo和bar两个字段- foo: 1 bar: 10 - foo: 3 bar: 100 - foo: 1 bar: 12.2 执行分组命令yq group_by(.foo) sample.yml2.3 输出结果- - foo: 1 bar: 10 - foo: 1 bar: 1 - - foo: 3 bar: 100结果解读所有foo 1的元素{foo: 1, bar: 10}和{foo: 1, bar: 1}被归入第一个子数组foo 3的元素{foo: 3, bar: 100}单独构成第二个子数组分组内部保留了元素在原始数组中的相对顺序bar: 10在bar: 1之前与原数组顺序一致。三、null 值处理字段缺失的元素如何分组当数组中的某些元素不包含分组表达式中引用的字段时这些元素的表达式求值结果为空group_by会将它们统一归入以null为 key 的分组。3.1 准备数据- cat: dog - foo: 1 bar: 10 - foo: 3 bar: 100 - no: foo for you - foo: 1 bar: 1这里{cat: dog}与{no: foo for you}两个元素都没有foo字段。3.2 执行同样的命令yq group_by(.foo) sample.yml3.3 输出结果- - cat: dog - no: foo for you - - foo: 1 bar: 10 - foo: 1 bar: 1 - - foo: 3 bar: 100结果解读两个缺少foo字段的元素被归入同一个 null 分组且该分组排在输出最前面——因为它对应的 key 在遍历中首次出现foo: 1和foo: 3的分组随后依次排列分组 key 使用表达式求值的第一个匹配节点的值作为标识缺字段时统一回落为字符串null。四、源码原理分组到底是怎么实现的group_by的完整实现位于 pkg/yqlib/operator_group_by.go核心逻辑分为两层入口的groupBy函数与逐元素分组的processIntoGroups函数。4.1 入口检查只支持数组for el : context.MatchingNodes.Front(); el ! nil; el el.Next() { candidate : el.Value.(*CandidateNode) if candidate.Kind ! SequenceNode { return Context{}, fmt.Errorf(only arrays are supported for group by) } ... }在groupBy中yq 会遍历所有匹配到的候选节点一旦发现当前节点不是 Sequence数组类型立即返回错误only arrays are supported for group by。这意味着对普通对象或标量执行group_by会直接失败这是与 jq 行为对齐的语义。4.2 逐元素求值分组func processIntoGroups(d *dataTreeNavigator, context Context, rhsExp *ExpressionNode, node *CandidateNode) (*orderedmap.OrderedMap, error) { var newMatches orderedmap.NewOrderedMap() for _, child : range node.Content { rhs, err : d.GetMatchingNodes(context.SingleReadonlyChildContext(child), rhsExp) ... keyValue : null if rhs.MatchingNodes.Len() 0 { first : rhs.MatchingNodes.Front() keyCandidate : first.Value.(*CandidateNode) keyValue keyCandidate.Value } groupList, exists : newMatches.Get(keyValue) if !exists { groupList list.New() newMatches.Set(keyValue, groupList) } groupList.(*list.List).PushBack(child) } return newMatches, nil }这段代码揭示了三个关键实现细节求值每个元素对数组中的每个子节点以该节点为上下文求值分组表达式rhsExp拿到匹配结果取第一个匹配值作为分组 keyrhs.MatchingNodes.Front()即第一个匹配节点其Value字符串被用作分组标识若没有任何匹配字段缺失key 默认回落为字符串null——这正是第三节中缺字段元素被归入同一组的原因保持插入顺序分组容器使用github.com/elliotchance/orderedmap的有序 Mapkey 第一次出现的位置即决定了该分组在最终输出中的顺序因此输出顺序严格遵循“首次出现顺序”而非排序顺序。4.3 组装输出结构resultNode : candidate.CreateReplacement(SequenceNode, !!seq, ) for groupEl : newMatches.Front(); groupEl ! nil; groupEl groupEl.Next() { groupResultNode : CandidateNode{Kind: SequenceNode, Tag: !!seq} groupList : groupEl.Value.(*list.List) for groupItem : groupList.Front(); groupItem ! nil; groupItem groupItem.Next() { groupResultNode.AddChild(groupItem.Value.(*CandidateNode)) } resultNode.AddChild(groupResultNode) }groupBy会为每个分组创建一个!!seq类型的子数组节点元素按原顺序PushBack进分组再将这些分组依次挂到结果数组下最终得到“数组的数组”这一输出形态。五、进阶用法与技巧5.1 分组后展开子数组group_by的输出是嵌套数组如需按组逐个处理可以配合[]splat 操作符展开。这一用法在 operator_group_by_test.go 的Group splat场景中有明确验证yq group_by(.foo)[] sample.yml输出会按组分别列出- foo: 1 bar: 10 - foo: 1 bar: 1 --- - foo: 3 bar: 1005.2 分组表达式不限于字段group_by的表达式参数可以是任意合法表达式例如按tag节点类型标签、length长度、key键名甚至复合表达式分组输出形态完全一致只是分组依据不同。5.3 与 unique_by 的对比group_by把相同 key 的元素聚成子数组完整保留所有元素unique_by只保留每组第一个元素其余丢弃用于去重。两者在实现上高度相似unique_by的实现pkg/yqlib/operator_unique.go同样遍历数组元素、求值表达式、以结果为 key 存入有序 Map区别仅在于unique_by遇到重复 key 时不再追加元素。若想了解去重场景可参考 pkg/yqlib/doc/operators/unique.md。5.4 多文档输入group_by会逐个处理上下文中的所有匹配节点因此面对多文档---分隔输入时会对每个文档中的数组分别执行分组输出同样按文档顺序排列。六、测试验证行为由测试用例锚定group_by的三种典型行为均有对应的单元测试位于 pkg/yqlib/operator_group_by_test.go测试场景表达式验证点Group by fieldgroup_by(.foo)按字段分组组内保持原顺序foo: 1组在前Group splatgroup_by(.foo)[]展开后逐组输出路径分别为P[0]、P[1]Group by field, with nullsgroup_by(.foo)缺字段元素统一落入 null 组且排在首位这些用例通过testScenario框架断言输出的节点路径如D0, P[]、类型标签!!seq与精确的 YAML 内容为group_by的行为提供了可回归验证的保障。七、小结group_by是 yq 处理数组分组任务的核心工具掌握以下要点即可熟练使用语法group_by(表达式)输入必须是数组输出是“数组的数组”null 分组表达式求值为空字段缺失的元素自动归入以null为 key 的分组顺序语义分组与组内元素均保持首次出现顺序不做排序组合用法可配合[]展开分组可按tag、length等任意表达式分组实现原理底层基于有序 Map 实现 key 聚合operator_group_by.go与unique_by共用同一套“按表达式求 key”的思想。在实际的配置管理与数据处理流水线中group_by常被用于按环境、按服务名、按状态字段聚合配置项再配合其他操作符做进一步加工是 yq 表达式体系中复用率极高的基础能力。【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考