nvancouver
nvancouver

Reputation: 147

Golang reflect/iterate through interface{}

I’m looking to iterate through an interfaces keys.

Goal:

Controller level

type Tag struct {
  Name string
}
type BaseModel struct {
  ID uuid.UUID
  Active bool
}
type Model struct {
  BaseModel // embedded struct
  Name string
  Number int
  Tags []Tag
}

newModel, err := GetModel()
if err != nil {
   ...
}

RespondAsJson(w, newModel)

Middleware / Middle man json responder

//(1) Attempting to use a map
func RespondWithJson(w http.ResponseWriter, data interface{}) {
   obj, ok := data.(map[string]interface{})
   // (1.1) OK == false

   obj, ok := data.(map[interface{}]interface{})
   // (1.2) OK == false

   var newMap map[string]interface{}
   bytes, _ := json.Marshal(&obj)
   json.unMarshal(bytes, &newMap)
   // (1.3) newMap has no underlying types on fields
   // Nil slice of Tags went in, and it comes out as
   // value=nil and type=interface{} 
}

//(2) Skipping two as I believe this works, I'd like to avoid implementing it though.
//(3) 
//(3.1) 

RespondWithJson(w, newModel)
func RespondWithJson(w http.ResponseWriter, data interface{}) {

   e := reflect.ValueOf(&data) // data = {*interface{} | Model}
   if e.Kind() == reflect.Pointer {
     e = e.Elem()
     // e has a flag of 22, e.Elem() has a flag of 404
   }
    for i := 0; i < e.NumField(); i++ {
//PANIC: reflect: call of reflect.Value.NumField on interface Value
    ...
    }
}
//(3.2) 
// Reference: https://go.dev/blog/laws-of-reflection (third law)

RespondWithJson(w, newModel)
func RespondWithJson(w http.ResponseWriter, data interface{}) {

   e := reflect.ValueOf(data) // data = {interface{} | Model}
   if e.Kind() == reflect.Pointer {
     e = e.Elem()
   }
    for i := 0; i < e.NumField(); i++ {
        field := e.Field(i)
        if field.Kind() == reflect.Slice && field.isNil() {
            ok := field.CanSet() // OK == false
         // Reference third law description in the reference above
            valueOfField1 := reflect.ValueOf(&field)
            ok := valueOfField1 .CanSet() // OK == false
            ...
            valueOfField2 := reflect.ValueOf(field.Interface())
            ok := valueOfField2.CanSet() // OK == false
            ...
        }
    }
}
//(3.3) 
// Reference: (https://stackoverflow.com/questions/64211864/setting-nil-pointers-address-with-reflections) and others like it
RespondWithJson(w, newModel)
func RespondWithJson(w http.ResponseWriter, data interface{}) {
  e := reflect.ValueOf(data) // {interface{} | Model}
  if e.Kind() == reflect.Pointer { e = e.Elem() }
  for i := 0; i < e.NumField(); i++ {
    field := e.Field(i)
    if field.Kind() == reflect.Slice && field.IsNil() {
      tmp := reflect.New(field.Type())
      if tmp.Kind() == reflect.Pointer { tmp = tmp.Elem()}

      // (3.3.1)
      ok := tmp.CanSet() // OK == true
      tmp.Set(reflect.MakeSlice(field.Type(),0,0))
      ok := field.CanSet()
      // OK == false, tmp.set doesn't affect field value && can't set 
      field with value of tmp
    
      // (3.3.2)
      ok := tmp.Elem().CanSet() 
      // PANIC - call of reflect.value.Elem on Slicevalue
      ...
    }

  }
}
//(3.4) 
// I can get it to work with passing &model to the function
// Once I'm inside the function, it's seen as an interface (or a
// *interface and the above is my results

RespondWithJson(w, &newModel)
func RespondWithJson(w http.ResponseWriter, data interface{}) {
   e := reflect.ValueOf(data) // Data is {interface{} | *Model}
   if e.Kind() == reflect.Pointer {
     e = e.Elem()
     // e has a flag of 22, e.Elem() has a flag of 409
   }
    for i := 0; i < e.NumField(); i++ {
        field := e.Field(i)
        if field.Kind() == reflect.Slice && field.IsNil() {
            ok := field.CanSet()
            // OK == true, field is addressable
            if ok {
                field.Set(reflect.MakeSlice(field.Type(), 0, 0))
                // Success! Tags: nil turned into Tags: []
            }
        }
    }
}

After that and many more.. random interations, I've found a way to make it work by passing memory address of struct to function which takes interface value.

If possible, I'd like to avoid the need to do this, as the function signature won't pick it up and it just leaves a small amount of room for error for other people on my team. I can of course just document the function, but its not bullet proof :)

Does anyone have suggestions for making this work without starting with a memory address to a struct? Can I set a field of an interface? ty very much!

Upvotes: 2

Views: 1154

Answers (1)

Woody1193
Woody1193

Reputation: 8010

In general, what you're probably looking for is something involving reflection. Your current code:

func someFunction(data interface{}) {
    y := reflect.ValueOf(&data)
    for i := 0; i < y.NumField(); i++ {
        // PANIC: y is not a value of a struct
    }
}

is pretty close, but it fails because data is a pointer. You can fix this by doing:

y := reflect.ValueOf(data)
if y.Kind() == reflect.Pointer {
    y = y.Elem()
}

This will ensure that you have the actual value, and not a pointer to the value, allowing you to do NumField on it. Inside the loop, you check if the field is a slice and if it's nil and then set it to the value of a new instance of a slice of your field's type.

yField := y.Field(i)
if yField.Kind() == reflect.Slice && yField.IsNil() {
    yField.Set(reflect.MakeSlice(yField.Elem().Type(), 0, 0)
}

Here we use Elem again because yField points to a slice, and so to create a new slice we need the inner type.

Finally, you need to add recursion to handle inner types if any of your fields are structs:

func SomeFunction(data interface{}) ([]byte, error) {
    someFunctionInner(reflect.ValueOf(data))
    return json.Marshal(data)
}

func someFunctionInner(v reflect.Value) {
    if v.Kind() == reflect.Pointer {
        v = v.Elem()
    }

    for i := 0; i < v.NumField(); i++ {
        vField := v.Field(i)

        switch vField.Kind() {
        case reflect.Slice:
            if vField.IsNil() {
                vField.Set(reflect.MakeSlice(vField.Type(), 0, 0))
            } else {
                for j := 0; j < vField.Len(); j++ {
                    vFieldInner := vField.Index(j)
                    if vFieldInner.Kind() != reflect.Struct &&
                        (vFieldInner.Kind() != reflect.Pointer || vFieldInner.Elem().Kind() != reflect.Struct) {
                        continue
                    }

                    someFunctionInner(vFieldInner.Index(j))
                }
            }
        case reflect.Pointer, reflect.Struct:
            someFunctionInner(vField)
        default:
        }
    }
}

and then you call it like this:

func main() {
    m := Model{}
    b, d := SomeFunction(&m)
    fmt.Printf("Data: %+v\n", m)
    fmt.Printf("JSON: %s, Error: %v\n", b, d)
}

Data: {BaseModel:{ID: Active:false} Name: Number:0 Tags:[]}
JSON: {"ID":"","Active":false,"Name":"","Number":0,"Tags":[]}, Error: <nil>

Note that I haven't added any sort of error-handling. Nor have I handled anything above regular pointers. Also, this function does expect a reference to an object because it is making modifications to said object. Finally, this code doesn't touch array logic at all. Still, this is likely what you're looking for.

Upvotes: 1

Related Questions