1

I've been struggling with the reflect package. This code below does what I expect:

package main

import (
  "reflect"
  "log"
)
type Car struct {
  Model string
}
type Person struct {
  Name string
  Cars []Car
}

func ModifyIt(parent interface{},fieldName string, val interface{}) {
  slice := reflect.ValueOf(parent).Elem()
  nth := slice.Index(0)
  //row := nth.Interface() // this line causes errors
  row := nth.Interface().(Person)
  elem := reflect.ValueOf(&row).Elem()
  field := elem.FieldByName(fieldName)
  log.Println(field.CanSet())

}

func main() {

  p := []Person{Person{Name:"john"}}
  c := []Car{Car{"corolla"},Car{"jetta"}}

  ModifyIt(&p,"Cars",&c)
}

However, if I replace the line row := nth.Interface().(Person) with row := nth.Interface(), that is I remove the type assertion, then I get the error:

panic: reflect: call of reflect.Value.FieldByName on interface Value on line "field := elem.FieldByName(fieldName)

I've tried a bunch of other things the last few hours like trying to do reflect.TypeOf(), reflect.Indirect() etc... on some of the other variables but with no success.

I've read some other questions like these:

reflect: call of reflect.Value.FieldByName on ptr Value

Set a struct field with field type of a interface

Golang reflection: Can't set fields of interface wrapping a struct

They seem to suggest that I don't have a good understanding of how pointers or interfaces work.

So my question is, how do I go about setting the field of a struct when the struct is typed as an interface?


UPDATE

I posted a solution as an answer, but I have no confidence in whether it is the proper or safe way of doing things. I hope someone can explain, or post a better solution.

John
  • 32,403
  • 80
  • 251
  • 422

2 Answers2

3

Try this:

func ModifyIt(slice interface{}, fieldName string, newVal interface{}) {
    // Create a value for the slice.
    v := reflect.ValueOf(slice)

    // Get the first element of the slice.
    e := v.Index(0)

    // Get the field of the slice element that we want to set.
    f := e.FieldByName(fieldName)

    // Set the value!
    f.Set(reflect.ValueOf(newVal))
}

Call it like this:

p := []Person{Person{Name: "john"}}
c := []Car{Car{"corolla"}, Car{"jetta"}}
ModifyIt(p, "Cars", c)

Note that the call passes the slices directly instead of using pointers to slices. The pointers are not needed and add extra complexity.

Run it on the Playground.

Charlie Tumahai
  • 113,709
  • 12
  • 249
  • 242
  • Later, I want to add some new elements to `v` within the `ModifyIt()` function. The variable `p` in the `main()` function will retain whatever value `v` had up until the line before I call `v = append(v,Person{Name:"Sara"})`. If I want `p` in `main()` to show the new items added to `v` in `ModifyIt()`, do I need to pass in `p` to `ModifyIt` as a pointer, then follow the solution I posted? – John Sep 01 '18 at 05:23
  • As follow up to my comment, I don't think passing in `p` as pointer or not will be related to my eventual question about `v`, `append` and `p` in `main()`. I'll have to play around more to understand how this works – John Sep 01 '18 at 05:43
  • I am uncertain about what you are trying to do. For example, what is it that you want to add? The best way to describe what you want is to update the question with the code for the specific types (Car, Person and slices of these types). I am having trouble deriving it from the code in the question or your answer. – Charlie Tumahai Sep 01 '18 at 05:43
  • If you want to add elements to the parent slice, then pass pointer to slice to ModifyIt and change first line of ModifyIt to `v := reflect.ValueOf(slice).Elem()`. Use `v.Set(reflect.Append(v, newValue))` to append to slice. – Charlie Tumahai Sep 01 '18 at 05:50
  • Thanks, your suggestion makes sense, but I'm still scratching my head around another entry level question which I just posted here https://stackoverflow.com/questions/52125578/using-reflect-to-update-value-by-reference-when-argument-is-not-a-pointer-in-go – John Sep 01 '18 at 06:40
0

Out of sheer luck, I finally got something to work.

I pieced together a bunch of random things I read with very little rhyme or reason. I even tried reading the Laws of Reflection on the Golang site, but I don't think I have a good grasp of how it relates to why I couldn't set variables typed as interface{}. In general, I still don't understand what I did.

My solution below is littered with comments to indicate my confusion, and lack of confidence in whether I did things properly or safely.

package main

import (
  "reflect"
  "log"
)
type Car struct {
  Model string
}
type Person struct {
  Name string
  Cars []Car
}

func ModifyIt(parent interface{},fieldName string, val interface{}) {
  log.Println(parent)
  slice := reflect.ValueOf(parent).Elem()
  nth := slice.Index(0)
  row := nth.Interface()
  log.Println(nth.CanSet()) // I can set this nth item

  // I think I have a to make a copy, don't fully understand why this is necessary
  newitem := reflect.New(reflect.ValueOf(row).Type())
  newelem := newitem.Elem()
  field := newelem.FieldByName(fieldName)

  // I need to copy the values over from the old nth row to this new item
  for c:=0; c<nth.NumField(); c++ {
    newelem.Field(c).Set(reflect.Indirect(nth.Field(c)))
  }

  // now I can finally set the field for some reason I don't understand
  field.Set(reflect.ValueOf(val).Elem())

  // now that newitem has new contents in the field object, I need to overwrite the nth item with new item
  // I don't know why I'm doing it, but I'll do it
  // I also don't fully understand why I have to use Indirect sometimes, and not other times...it seems interchangeable with ValueOf(something).Elem(), I'm confused....
  nth.Set(reflect.Indirect(newitem))

}

func main() {

  p := []Person{Person{Name:"john"}}
  c := []Car{Car{"corolla"},Car{"jetta"}}

  ModifyIt(&p,"Cars",&c)
  // now parent is up to date, although I have no idea how I got here.
  log.Println(p)
}

If anyone can post a better answer that clears up my confusion, that will be great. I've been having a really hard time learning golang.

John
  • 32,403
  • 80
  • 251
  • 422