learningtech
learningtech

Reputation: 33683

Can't set field of a struct that is typed as an interface{}

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.

Upvotes: 1

Views: 2246

Answers (2)

Thundercat
Thundercat

Reputation: 120941

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.

Upvotes: 3

learningtech
learningtech

Reputation: 33683

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.

Upvotes: 0

Related Questions