Reputation: 61
In the process of writing a generic diff and patch algorithm I faced a problem with reflection in go.
When I'm trying to patch in a slice I have no problem, reflect.ValueOf(&slice).Elem().Index(0).CanSet()
returns true. This allows me to patch anything inside the slice element, be it a slice of primitive or of structs.
However when I attempt this with a map reflect.ValueOf(&map).Elem().MapIndex(reflect.ValueOf("key")).CanSet()
returns false. This prevents me from attempting to do anythnin with the content of my map.
Slice
s := []string{"a", "b", "c"}
v := reflect.ValueOf(&s).Elem()
e := v.Index(1)
println(e.String())
println(e.CanSet())
e.Set(reflect.ValueOf("d"))
for _, v := range s {
print(v, " ")
}
output :
b
true
a d c
Map
m := map[string]string{
"a": "1",
"b": "2",
"c": "3"}
mv := reflect.ValueOf(&m).Elem()
println(mv.MapIndex(reflect.ValueOf("a")).CanSet())
output:
false
How could I get a modifiable value out of a map through reflection?
Thanks for your time.
Upvotes: 4
Views: 2584
Reputation: 417592
This is not a limitation of the reflect
package. Slice index expressions are addressable (e.g. &s[0]
is valid for example), so slice elements obtained via reflection will be settable. Map index expressions are not addressable (e.g. &m["a"]
is invalid), so values of keys obtained via reflection will not be settable. See related How to update map values in Go
Only addressable values are settable, attempting to "set" a non-addressable value could only modify a copy (and not the original value), so it's not allowed in the first place. Quoting from Value.CanSet()
:
CanSet reports whether the value of v can be changed. A Value can be changed only if it is addressable and was not obtained by the use of unexported struct fields.
If you want to change the value assigned to a key in a map using reflection, use the Value.SetMapIndex()
method:
mv.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf("11"))
fmt.Println(m)
Output will be (try it on the Go Playground):
map[b:2 c:3 a:11]
Note: taking the address of a map index expression (and allowing to change a value using reflection) is not allowed because the internals of a map may change at any time (e.g. if new key-values are added to the map, the implementation may have to re-structure the way it stores the key-value pairs internally), so a possible pointer you would obtain by &m["a"]
may not exist (or may point to another value) by the time you end up using it. To avoid such confusions and run-time panics, this is not allowed in the first place.
Upvotes: 6