Golang给深层嵌套结构赋值

huangapple go评论92阅读模式
英文:

Golang assign value to deep nested structure

问题

我正在学习Go,并且目前非常喜欢它。作为一个有JavaScript背景的人,我仍在探索某些模式和最佳实践。

在Go中,使用对象路径获取并赋值给一个深层嵌套的对象的最佳方法是什么?例如,在JavaScript中可以这样做...

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)
英文:

I'm learning Go and so far really enjoying it. Coming from a JS background, there are certain patterns and best practices that I'm still discovering.

What would be the best way to get and assign a value to a deeply nested object in Go using a Object path? For example, in JS it could be done like this...

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

var children = [{children:[{children:[{a:1}]}]}]
var child = &quot;0.children.0.children.0&quot;.split(&#39;.&#39;).reduce((c, p) =&gt; c

, children) child.a = 2 console.log(children[0].children[0].children[0].a)

<!-- end snippet -->

答案1

得分: 6

如果您需要一个通用的解决方案,可以使用reflect包来实现,但如果可能的话最好避免使用它(例如,如果您在编译时知道类型和“路径”,可以直接使用字段选择器索引表达式)。

下面是一个示例。一个设置由string元素指定的“深层”值的辅助函数可以如下所示:

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}]

从输出结果可以看出,由字符串路径`"0.Children.0.Children.0.A"`指定的“深层”字段`A`从初始值`1`变为`2`。

请注意,结构体字段(在本例中为`Foo.A`和`Foo.Children`)必须是导出的(必须以大写字母开头),否则其他包将无法访问这些字段,并且无法使用`reflect`包更改它们的值。

---

在不使用反射的情况下,如果事先知道类型和“路径”,可以像这样实现(继续上面的示例):

```go
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}

这是不使用反射的通用解决方案:

```go
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`切片会更合理。

  [1]: https://golang.org/pkg/reflect/
  [2]: https://golang.org/ref/spec#Selectors
  [3]: https://golang.org/ref/spec#Index_expressions
  [4]: https://play.golang.org/p/i36wueuYgG
  [5]: https://play.golang.org/p/y0x3JJjUO0
  [6]: https://play.golang.org/p/QABDDX0Rz1
英文:

If you need a general solution, you can do it using package 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).

Here's a demonstration. A helper function that sets a "deep" value specified by string elements may look like this:

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))
}

The index() function used above may look like this:

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)
}

This is how we can test it:

type Foo struct {
	Children []Foo
	A        int
}

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

Output (try it on the Go Playground):

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

As can be seen from the output, the "deep" field A denoted by the string path &quot;0.Children.0.Children.0.A&quot; changed from the initial 1 to 2.

Note that fields of structs (Foo.A and Foo.Children in this case) must be exported (must start with capital letter), else other packages would not be able to access those fields, and their value could not be changed using package reflect.


Without reflection, knowing the types and "path" prior, it can be done like this (continuing the previous example):

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

Output (try it on the Go Playground):

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

The general solution of this (without reflection):

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

Using it (again, continuing the previous example):

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

Output (try it on the Go Playground):

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

But note that if we're only dealing with int indices, it does not make sense anymore to declare path to be ...string (which is []string), an int slice would make a lot more sense.

huangapple
  • 本文由 发表于 2016年12月27日 10:36:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/41338339.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定