我正在寻找迭代通过一个接口键.

目标:

  • 我想实现一种中间件,它可以判断传出数据(编组为JSON),并将nil切片编辑为空切片.
  • 它应该是不可知的/通用的,这样我就不需要指定字段名.理想情况下,我可以将任何 struct 作为接口传递,并用空片替换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: []
            }
        }
    }
}

在那之后还有更多..随机迭代,我已经找到了一种方法,通过将 struct 的内存地址传递给函数,该函数接受接口值.

如果可能的话,我希望避免这样做的需要,因为函数签名不会提取它,并且它只给我的团队中的其他人留下了很小的出错空间.当然,我可以只记录该函数,但它不是万无一失的:)

有没有人有建议在不从 struct 的内存地址开始的情况下使其工作?我可以设置界面的字段吗?太感谢你了!

推荐答案

一般而言,您可能正在寻找的是涉及到反思的东西.您当前的代码:

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.在循环中,判断该字段是否为片,是否为空,然后将其设置为您的字段类型的片的新实例的值.

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

这里我们再次使用Elem,因为yField指向一个切片,因此要创建一个新的切片,我们需要内部类型.

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

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: %+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>

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

Go相关问答推荐

Go 1.22 http mux:在同一路径上提供一个手柄和一个FS

使用恶意软件 scanner (ClamAV)时的Google云存储桶上传文件验证

戈姆:如何将一对一联系起来?

为什么没有正确生成这些元组?

从MySQL/GO表获取行数据

提供的client_secret与此帐户上任何关联的SetupIntent都不匹配

go中跨域自定义验证的问题

将这两个函数合二为一的惯用方法

无法读取postman 中的表单数据

Golang Fiber Render - 将数据发送到多个布局

如何根据地址和大小打印字符串

为什么我只收到部分错误而不是我启动的 goroutines 的所有错误?

Golang:如何在不转义每个动作的情况下呈现模板的模板?

致命错误 - 所有 Goroutines 都睡着了!僵局

接受通道和切片的通用函数

数据流中的无根单元错误,从 Golang 中的 PubSub 到 Bigquery

GqlGen - 在字段解析器中访问查询输入参数

Go 使用 struct 作为接口而不实现所有方法

传递上下文的最佳方式

如果在调用 http.Get(url) 时发生错误,我们是否需要关闭响应对象?