
本文介绍使用 gopkg.in/yaml.v2 解析 YAML 后,通过反射(reflect)递归遍历任意深度嵌套的 map 和 slice,安全打印所有键值对,适用于动态、未知结构的 YAML 配置解析。
本文介绍使用 `gopkg.in/yaml.v2` 解析 yaml 后,通过反射(`reflect`)递归遍历任意深度嵌套的 map 和 slice,安全打印所有键值对,适用于动态、未知结构的 yaml 配置解析。
在 Go 中处理 YAML 文件时,若其结构不固定(如存在多层嵌套的 map、slice、标量混合),无法预先定义 struct,此时应采用泛型解组方式:将 YAML 解析为 map[string]interface{}(推荐)或 map[interface{}]interface{},再结合 reflect 包进行类型判断与递归遍历。
以下是一个完整、健壮的遍历示例,支持:
- 顶层键(如 Data)
- slice 元素(YAML 中的 - 列表项)
- 嵌套 map(如 bar1, k1 等)
- 字符串、整数等基础类型输出
- 可控缩进,直观呈现层级关系
package main
import (
"fmt"
"gopkg.in/yaml.v2"
"reflect"
"strings"
)
var data = `
Data:
- name: "foo"
bar1: 0
k1: val1
k2:
- val2
- val3
bar2: 1
k3: val4
k4: val5
k5: val5
k6: val6
`
// printVal 递归打印任意类型的值,按 depth 缩进
func printVal(v interface{}, depth int) {
rv := reflect.ValueOf(v)
if !rv.IsValid() {
fmt.Printf("%s<nil>\n", strings.Repeat(" ", depth))
return
}
switch rv.Kind() {
case reflect.String, reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8,
reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32,
reflect.Float64, reflect.Bool:
fmt.Printf("%s%v\n", strings.Repeat(" ", depth), rv.Interface())
case reflect.Map:
if rv.Len() == 0 {
fmt.Printf("%s(map empty)\n", strings.Repeat(" ", depth))
return
}
// 转换为 map[interface{}]interface{} 以兼容 yaml.v2 默认行为
m := rv.Convert(reflect.TypeOf(map[interface{}]interface{}{})).Interface().(map[interface{}]interface{})
fmt.Printf("\n")
printMap(m, depth+1)
case reflect.Slice, reflect.Array:
if rv.Len() == 0 {
fmt.Printf("%s([] empty)\n", strings.Repeat(" ", depth))
return
}
slc := make([]interface{}, rv.Len())
for i := 0; i < rv.Len(); i++ {
slc[i] = rv.Index(i).Interface()
}
fmt.Printf("\n")
printSlice(slc, depth+1)
default:
fmt.Printf("%s<unsupported: %s>\n", strings.Repeat(" ", depth), rv.Kind())
}
}
func printMap(m map[interface{}]interface{}, depth int) {
for k, v := range m {
keyStr := fmt.Sprintf("%v", k)
fmt.Printf("%sKey: %s → ", strings.Repeat(" ", depth), keyStr)
printVal(v, depth+1)
}
}
func printSlice(slc []interface{}, depth int) {
for i, v := range slc {
fmt.Printf("%s[%d] → ", strings.Repeat(" ", depth), i)
printVal(v, depth+1)
}
}
func main() {
var m map[string]interface{}
err := yaml.Unmarshal([]byte(data), &m)
if err != nil {
panic(fmt.Sprintf("YAML unmarshal failed: %v", err))
}
for k, v := range m {
fmt.Printf("Top-level Key: %s →\n", k)
printVal(v, 1)
}
}✅ 关键说明与注意事项:
- yaml.v2 默认将 map 解组为 map[interface{}]interface{},但为简化顶层访问,建议声明为 map[string]interface{};若 YAML 键含非字符串(极少见),需改用 map[interface{}]interface{} 并统一处理。
- printVal 使用 reflect.ValueOf 安全获取值,并显式处理 nil 和不支持类型,避免 panic。
- slice 中的元素可能为任意类型(包括 map 或 slice),因此必须递归调用 printVal,而非直接断言 []interface{} —— 示例中已通过 reflect 动态转为切片并遍历。
- 实际项目中,可将遍历逻辑封装为函数(如 WalkYAML(func(path string, value interface{}) error)),支持路径追踪(如 Data.0.name)和中断控制。
- 若需修改 YAML 值,注意 interface{} 是只读副本;如需写入,应基于结构体或使用 gopkg.in/yaml.v3 的 Node API。
掌握该模式后,即可灵活解析 CI 配置、Kubernetes manifests、自定义 DSL 等高度动态的 YAML 场景。

















