Home > other >  Golang reflect/iterate through interface{}
Golang reflect/iterate through interface{}

Time:12-26

I’m looking to iterate through an interfaces keys.

Goal:

  • I want to implement a kind of middleware that checks for outgoing data (being marshalled to JSON) and edits nil slices to empty slices.
  • It should be agnostic/generic so that I don't need to specify field names. Ideally I can pass any struct as an interface and replace nil slices with empty slices.

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

  • It takes an interface to be generic/agnostic and reusable in many different controllers
//(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!

CodePudding user response:

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.

  • Related