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.A
和 Foo.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
切片会更有意义。
我正在学习围棋,到目前为止我非常喜欢它。来自 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.A
和 Foo.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
切片会更有意义。