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("%+v
", x)
path := "0.Children.0.Children.0.A"
set(x, 2, strings.Split(path, ".")...)
fmt.Printf("%+v
", 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 "0.Children.0.Children.0.A"
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 := &x[0].Children[0].Children[0]
fmt.Printf("%+v
", f)
f.A = 3
fmt.Printf("%+v
", f)
Output (try it on the Go Playground):
&{Children:[] A:2}
&{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 = &x[i]
x = f.Children
}
}
return
}
Using it (again, continuing the previous example):
path = "0.0.0"
f2 := getFoo(x, strings.Split(path, ".")...)
fmt.Printf("%+v
", f2)
f2.A = 4
fmt.Printf("%+v
", f2)
Output (try it on the Go Playground):
&{Children:[] A:3}
&{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.