首页 > 文章列表 > 使用Golang的接口进行反射/迭代{}

使用Golang的接口进行反射/迭代{}

257 2024-02-04
问题内容

我正在寻找迭代接口键。

目标:

  • 我想实现一种中间件,用于检查传出数据(编组为 json)并将 nil 切片编辑为空切片。
  • 它应该是不可知的/通用的,这样我就不需要指定字段名称。理想情况下,我可以将任何结构作为接口传递,并用空切片替换 nil 切片。

控制器级别

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)

中间件/中间人 json 响应器

  • 接口需要通用/不可知且可重用 在许多不同的控制器中
//(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: []
            }
        }
    }
}

之后以及更多......随机交互,我找到了一种方法,通过将结构的内存地址传递给采用接口值的函数来使其工作。

如果可能的话,我想避免这样做,因为函数签名不会接受它,并且只会为我团队中的其他人留下少量的错误空间。我当然可以只记录该功能,但它不是防弹的:)

有人建议在不从结构的内存地址开始的情况下完成这项工作吗?接口可以设置字段吗?非常喜欢!


正确答案


一般来说,您可能正在寻找涉及反射的东西。您当前的代码:

func somefunction(data interface{}) {
    y := reflect.valueof(&data)
    for i := 0; i < y.numfield(); i++ {
        // panic: y is not a value of a struct
    }
}

非常接近,但它失败了,因为 data 是一个指针。您可以通过执行以下操作来解决此问题:

y := reflect.valueof(data)
if y.kind() == reflect.pointer {
    y = y.elem()
}

这将确保您拥有实际值,而不是指向该值的指针,从而允许您对其执行 numfield 。在循环内,您检查该字段是否为切片,是否为 nil,然后将其设置为字段类型切片的新实例的值。

yfield := y.field(i)
if yfield.kind() == reflect.slice && yfield.isnil() {
    yfield.set(reflect.makeslice(yfield.elem().type(), 0, 0)
}

这里我们再次使用 elem 因为 yfield 指向一个切片,因此要创建一个新切片,我们需要内部类型。

最后,如果您的任何字段是结构,则需要添加递归来处理内部类型:

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:
        }
    }
}

然后你这样称呼它:

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

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

请注意,我没有添加任何类型的错误处理。我也没有处理过任何超出常规指针的事情。此外,该函数确实需要对对象的引用,因为它正在对所述对象进行修改。最后,这段代码根本不涉及数组逻辑。不过,这可能正是您正在寻找的。