Golang 反射/遍历接口{}

huangapple go评论79阅读模式
英文:

Golang reflect/iterate through interface{}

问题

我正在寻找一种遍历接口键的方法。

目标:

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

控制器级别

type Tag struct {
  Name string
}
type BaseModel struct {
  ID uuid.UUID
  Active bool
}
type Model struct {
  BaseModel // 嵌入的结构体
  Name string
  Number int
  Tags []Tag
}

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

RespondAsJson(w, newModel)

中间件/中间人JSON响应器

  • 它接受一个接口以实现通用性和重用性
    在许多不同的控制器中
//(1) 尝试使用映射
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的字段没有底层类型
   // 空的Tags切片进入,出来时值为nil,类型为interface{}
}

//(2) 跳过两个,我相信这个可以工作,但我想避免实现它。
```go
//(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的标志为22,e.Elem()的标志为404
   }
	for i := 0; i < e.NumField(); i++ {
//PANIC: reflect: call of reflect.Value.NumField on interface Value
    ...
	}
}
```go
//(3.2) 
// 参考:https://go.dev/blog/laws-of-reflection(第三定律)

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
         // 参考上面的第三定律描述
            valueOfField1 := reflect.ValueOf(&field)
            ok := valueOfField1 .CanSet() // OK == false
            ...
            valueOfField2 := reflect.ValueOf(field.Interface())
            ok := valueOfField2.CanSet() // OK == false
            ...
        }
	}
}
```go
//(3.3) 
// 参考:(https://stackoverflow.com/questions/64211864/setting-nil-pointers-address-with-reflections)和类似的其他问题
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不影响field的值,无法设置field的值为tmp的值
    
      // (3.3.2)
      ok := tmp.Elem().CanSet() 
      // PANIC - 在Slicevalue上调用reflect.value.Elem
      ...
    }

  }
}
```go
//(3.4) 
// 我可以通过将&model传递给函数来使其工作
// 一旦我进入函数,它被视为一个接口(或*interface,上面是我的结果)

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的标志为22,e.Elem()的标志为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是可寻址的
			if ok {
				field.Set(reflect.MakeSlice(field.Type(), 0, 0))
                // 成功!Tags: nil变为Tags: []
			}
		}
	}
}

在此之后,经过许多更多的随机尝试,我发现了一种通过将结构体的内存地址传递给接受接口值的函数来使其工作的方法。

如果可能的话,我希望避免这样做,因为函数签名不会捕捉到它,并且这只给我的团队中的其他人留下了一小部分错误的空间。当然,我可以记录函数,但它并不是百分之百可靠的 Golang 反射/遍历接口{}

有人有没有建议可以在不从结构体的内存地址开始的情况下使其工作?我可以设置接口的字段吗?非常感谢!

英文:

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 Golang 反射/遍历接口{}

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!

答案1

得分: 1

通常情况下,你可能正在寻找涉及反射的解决方案。你当前的代码:

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

请注意,我没有添加任何错误处理。我也没有处理普通指针之上的任何内容。此外,该函数期望一个对象的引用,因为它对该对象进行修改。最后,此代码不涉及数组逻辑。不过,这可能是你正在寻找的解决方案。

英文:

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

func someFunction(data interface{}) {
y := reflect.ValueOf(&amp;data)
for i := 0; i &lt; 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 &amp;&amp; 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 &lt; 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 &lt; vField.Len(); j++ {
vFieldInner := vField.Index(j)
if vFieldInner.Kind() != reflect.Struct &amp;&amp;
(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(&amp;m)
fmt.Printf(&quot;Data: %+v\n&quot;, m)
fmt.Printf(&quot;JSON: %s, Error: %v\n&quot;, b, d)
}
Data: {BaseModel:{ID: Active:false} Name: Number:0 Tags:[]}
JSON: {&quot;ID&quot;:&quot;&quot;,&quot;Active&quot;:false,&quot;Name&quot;:&quot;&quot;,&quot;Number&quot;:0,&quot;Tags&quot;:[]}, Error: &lt;nil&gt;

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.

huangapple
  • 本文由 发表于 2022年12月22日 10:57:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/74883746.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定