Golang为深层嵌套结构赋值

Golang assign value to deep nested structure

我正在学习围棋,到目前为止我非常喜欢它。来自 JS 背景,我仍在发现某些模式和最佳实践。

在 Go 中使用对象路径获取深度嵌套对象并为其赋值的最佳方法是什么?例如,在 JS 中可以这样做...

var children = [{children:[{children:[{a:1}]}]}]
var child = "0.children.0.children.0".split('.').reduce((c, p) => c[p], children)
child.a = 2
console.log(children[0].children[0].children[0].a)

如果你需要一个通用的解决方案,你可以使用包 reflect, but it's better to avoid it if possible (e.g. if you know the types and the "path" at compile time, simply use field selectors and index expressions).

这是一个演示。设置由 string 元素指定的 "deep" 值的辅助函数可能如下所示:

func set(d interface{}, value interface{}, path ...string) {
    v := reflect.ValueOf(d)
    for _, s := range path {
        v = index(v, s)
    }
    v.Set(reflect.ValueOf(value))
}

上面使用的index()函数可能是这样的:

func index(v reflect.Value, idx string) reflect.Value {
    if i, err := strconv.Atoi(idx); err == nil {
        return v.Index(i)
    }
    return v.FieldByName(idx)
}

我们可以这样测试它:

type Foo struct {
    Children []Foo
    A        int
}

func main() {
    x := []Foo{
        {
            Children: []Foo{
                {
                    Children: []Foo{
                        {
                            A: 1,
                        },
                    },
                },
            },
        },
    }
    fmt.Printf("%+v\n", x)
    path := "0.Children.0.Children.0.A"
    set(x, 2, strings.Split(path, ".")...)
    fmt.Printf("%+v\n", x)
}

输出(在 Go Playground 上尝试):

[{Children:[{Children:[{Children:[] A:1}] A:0}] A:0}]
[{Children:[{Children:[{Children:[] A:2}] A:0}] A:0}]

从输出可以看出,string路径"0.Children.0.Children.0.A"表示的"deep"字段A由最初的1变为2.

请注意,结构的字段(Foo.AFoo.Children 在这种情况下)必须导出(必须以大写字母开头),否则其他包将无法访问这些字段,并且使用包 reflect.

无法更改它们的值

不用反射,事先知道类型和"path",可以这样做(继续前面的例子):

f := &x[0].Children[0].Children[0]
fmt.Printf("%+v\n", f)
f.A = 3
fmt.Printf("%+v\n", f)

输出(在 Go Playground 上尝试):

&{Children:[] A:2}
&{Children:[] A:3}

这个的一般解决方案(没有反射):

func getFoo(x []Foo, path ...string) (f *Foo) {
    for _, s := range path {
        if i, err := strconv.Atoi(s); err != nil {
            panic(err)
        } else {
            f = &x[i]
            x = f.Children
        }
    }
    return
}

使用它(再次,继续前面的例子):

path = "0.0.0"
f2 := getFoo(x, strings.Split(path, ".")...)
fmt.Printf("%+v\n", f2)
f2.A = 4
fmt.Printf("%+v\n", f2)

输出(在 Go Playground 上尝试):

&{Children:[] A:3}
&{Children:[] A:4}

但请注意,如果我们只处理 int 索引,则将 path 声明为 ...string 不再有意义(即 []string ),int 切片会更有意义。