ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

yq flatten 操作符完全指南:递归展平嵌套数组的原理与实战

yq flatten 操作符完全指南:递归展平嵌套数组的原理与实战 yq flatten 操作符完全指南递归展平嵌套数组的原理与实战【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yqflatten是 yq 中用于将嵌套数组递归展平的专用操作符它能把多层嵌套的序列结构拍平为一层并支持通过flatten(N)精确控制展平深度。本文以仓库文档 pkg/yqlib/doc/operators/flatten.md 为主线结合 operator_flatten.go 的源码实现与 operator_flatten_test.go 的测试用例系统讲解其语法、默认行为、深度控制、边界情况以及处理对象数组等典型场景帮助你彻底掌握在 yq 中处理嵌套数组的能力。flatten 是什么flatten是一个递归展平数组的操作符。文档开篇即给出其定义This recursively flattens arrays递归展平数组。它的作用是把嵌套的数组序列展开消除层级结构让所有元素平铺在同一个数组层级中。在 yq 中flatten有两种调用形式语法含义flatten递归展平所有层级的数组直至完全展平flatten(N)仅展平到指定深度 NN 为非负整数从词法分析器的定义可以确认这两种形式// pkg/yqlib/lexer_participle.go {FlattenWithDepth, flatten\([0-9]\), flattenWithDepth(), 0}, {Flatten, flatten, opTokenWithPrefs(flattenOpType, nil, flattenPreferences{depth: -1}), 0},不带参数时flatten被解析为flattenPreferences{depth: -1}-1表示不限制深度无限递归带参数时则通过flattenWithDepth()解析括号内的数字并存入flattenPreferences{depth: depth}。从操作符注册表看flatten的完整定义位于 pkg/yqlib/operation.go#L197var flattenOpType operationType{Type: FLATTEN_BY, NumArgs: 0, Precedence: 52, Handler: flattenOp, CheckForPostTraverse: true}该操作符的处理器为flattenOp且标记了CheckForPostTraverse: true意味着它在执行后会触发后续遍历也就是测试中出现的flatten[]分裂形式详见下文与 splat 操作符的组合。基本用法递归展平所有层级示例完全展平文档给出了第一个也是最核心的示例。假设sample.yml内容如下- 1 - - 2 - - - 3即顶层是一个数组包含元素1、子数组[2]、孙数组[[3]]。执行yq flatten sample.yml输出结果为- 1 - 2 - 3可以看到嵌套在两层数组中的2和3都被提取出来与1平级排列。对应的测试用例在 pkg/yqlib/operator_flatten_test.go#L7-L16{ description: Flatten, subdescription: Recursively flattens all arrays, document: [1, [2], [[3]]], expression: flatten, expected: []string{ D0, P[], (!!seq)::[1, 2, 3]\n, }, },测试用 JSON 形式[1, [2], [[3]]]验证了同样结论结果为[1, 2, 3]展平后的节点仍然是序列!!seq。示例展平空数组文档还专门验证了一个边界情况——当数组中包含空数组时。假设sample.yml内容为- []执行yq flatten sample.yml输出结果为[]也就是说[[]]展平后得到[]——空数组被展平后不会留下任何残留元素结果是一个空数组。对应测试 pkg/yqlib/operator_flatten_test.go#L48-L54{ description: Flatten empty array, document: [[]], expression: flatten, expected: []string{ D0, P[], (!!seq)::[]\n, }, },示例展平对象数组flatten不仅适用于纯标量数组对包含对象的数组同样有效。假设sample.yml内容为- foo: bar - - foo: baz顶层数组由对象{foo: bar}和嵌套数组[{foo: baz}]组成。执行yq flatten sample.yml输出结果为- foo: bar - foo: baz嵌套数组中的对象{foo: baz}被提升到顶层两个对象平级排列。对应测试 pkg/yqlib/operator_flatten_test.go#L56-L62{ description: Flatten array of objects, document: [{foo: bar}, [{foo: baz}]], expression: flatten, expected: []string{ D0, P[], (!!seq)::[{foo: bar}, {foo: baz}]\n, }, },控制展平深度flatten(N)有时我们并不想把数组完全展平而只希望消除一层或几层嵌套。flatten(N)允许你指定展平深度。示例只展平一层文档示例中sample.yml内容为- 1 - - 2 - - - 3执行yq flatten(1) sample.yml输出结果为- 1 - 2 - - 3与完全展平的结果对比2从[2]中被提取出来但3仍保留在嵌套数组[3]中——因为[[3]]需要两次展平才能把3提到顶层而flatten(1)只做了一层。对应测试 pkg/yqlib/operator_flatten_test.go#L29-L35{ description: Flatten with depth of one, document: [1, [2], [[3]]], expression: flatten(1), expected: []string{ D0, P[], (!!seq)::[1, 2, [3]]\n, }, },深度参数的限制从词法规则可以看出flatten(N)中的 N 必须是非负整数// pkg/yqlib/lexer_participle.go {FlattenWithDepth, flatten\([0-9]\), flattenWithDepth(), 0},正则flatten\([0-9]\)只匹配十进制数字因此不能传入负数、小数或变量表达式。不过extractNumberParameter的正则本身支持负数-?[0-9]只是词法层面已经用[0-9]做了约束。如果不带参数直接写flatten深度会被设为-1语义是无限深度即完全展平。extractNumberParameter的实现位于 pkg/yqlib/lexer.go#L63-L71它从形如flatten(1)的原始 token 中提取括号内的数字func extractNumberParameter(value string) (int, error) { parameterParser : regexp.MustCompile(.*\((-?[0-9])\)) matches : parameterParser.FindStringSubmatch(value) var indent, errParsingInt parseInt(matches[1]) ... return indent, nil }底层实现原理flatten的核心逻辑在 pkg/yqlib/operator_flatten.go 中由flattenOp处理器和递归辅助函数flatten两部分组成。操作符入口 flattenOpfunc flattenOp(_ *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { log.Debugf(flatten Operator) depth : expressionNode.Operation.Preferences.(flattenPreferences).depth 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 flatten) } flatten(candidate, depth) } return context, nil }要点如下深度偏好flattenPreferences.depth由词法分析阶段写入表达式节点的Operation.Preferences执行时直接取出。遍历当前上下文中所有匹配节点对每个匹配的候选节点执行展平。类型检查如果候选节点不是序列SequenceNode即数组会直接返回错误only arrays are supported for flatten——也就是说flatten只能作用于数组不能对映射map或标量执行。递归展平函数 flattenfunc flatten(node *CandidateNode, depth int) { if depth 0 { return } if node.Kind ! SequenceNode { return } content : node.Content newSeq : make([]*CandidateNode, 0) for i : 0; i len(content); i { if content[i].Kind SequenceNode { flatten(content[i], depth-1) for j : 0; j len(content[i].Content); j { newSeq append(newSeq, content[i].Content[j]) } } else { newSeq append(newSeq, content[i]) } } node.Content make([]*CandidateNode, 0) node.AddChildren(newSeq) }这个递归函数揭示了几条关键语义深度为 0 时直接返回这是递归的终止条件也是flatten(1)只展平一层的原因——对顶层数组深度为 1展开一层后递归进入子数组时深度变为 0停止继续展开。只处理序列节点遇到非序列元素标量、映射原样保留。就地替换遍历原内容遇到序列子元素就先递归展平它然后把它的全部子元素平铺进newSeq非序列元素直接追加。最后清空原node.Content并用AddChildren(newSeq)重建实现就地展平。无限深度的实现不带参数时depth -1由于depth 0永远不会触发每次递归都做depth-1从 -1 一路递减递归会一直进行到所有嵌套序列都被展开为止。从 pkg/yqlib/operation.go#L197 可以看到flattenOpType声明了Precedence: 52在操作符优先级表中处于较高位置这保证了flatten与其他操作符组合时拥有确定的求值顺序。与 splat 操作符的组合flatten[]源码中还记录了flatten与 splat[]组合的特殊形态虽然文档正文未展开但测试给出了明确语义。例如// pkg/yqlib/operator_flatten_test.go { description: Flatten splat, skipDoc: true, document: [1, [2], [[3]]], expression: flatten[], expected: []string{ D0, P[0], (!!int)::1\n, D0, P[1], (!!int)::2\n, D0, P[2], (!!int)::3\n, }, },flatten[]会先展平数组再把展平后的每个元素分裂成独立节点输出对应CheckForPostTraverse: true的语义。带深度参数的形态flatten(1)[]则只分裂一层展平后的结果{ description: Flatten with depth and splat, skipDoc: true, document: [1, [2], [[3]]], expression: flatten(1)[], expected: []string{ D0, P[0], (!!int)::1\n, D0, P[1], (!!int)::2\n, D0, P[2], (!!seq)::[3]\n, }, },从源码结构看这种展平后逐元素输出的行为可以用于对数组每个元素继续施加后续管道操作是批量处理嵌套数组的实用技巧。这两个用例在测试中标记了skipDoc: true说明它们是源码内部验证的组合场景但完全可以在你的表达式中直接使用。实战建议与注意事项处理非数组输入的报错由于flattenOp会检查节点类型对非数组输入会返回only arrays are supported for flatten错误。因此在使用前应确认目标节点是数组例如先通过type或tag操作符判断类型或确保查询路径一定指向数组字段。深度参数的选取需要完全拍平多层嵌套时直接用flatten。只需要消除一层容器结构例如把数组的数组转成数组时用flatten(1)可以避免误伤更深层的合法嵌套。深度参数必须是字面量非负整数不能用变量或表达式代替词法层flatten\([0-9]\)的限制。与其他操作符的管道组合由于flatten返回展平后的数组你可以把它接在管道中继续处理例如yq [.a[]] | flatten | unique sample.yml yq .matrix.include | flatten(1) | map(.name) sample.yml具体组合取决于你的数据结构flatten的输出始终是序列可作为后续操作符的输入。测试验证仓库在 pkg/yqlib/operator_flatten_test.go 中通过TestFlattenOperatorScenarios覆盖了本文所述全部场景完全展平、深度展平、空数组、对象数组、splat 组合并同步生成文档用例documentOperatorScenarios(t, flatten, flattenOperatorScenarios)。你在修改或验证自己表达式时可以参考这些用例组织测试数据。小结flatten是 yq 处理嵌套数组的核心操作符无参形式递归展平全部层级flatten(N)精确控制展平深度空数组与对象数组都能正确处理。其底层实现在 operator_flatten.go 中通过递归 深度计数实现就地展平词法层在 lexer_participle.go 中区分flatten与flatten(N)两种语法并有完整的 测试用例 保证行为稳定。掌握它的默认语义、深度控制与类型约束你就能在各种 YAML、JSON 数据处理场景中高效地整理嵌套数组。【免费下载链接】yqyq is a portable command-line YAML, JSON, XML, CSV, TOML, HCL and properties processor项目地址: https://gitcode.com/GitHub_Trending/yq/yq创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表