我正在寻找迭代接口键。
目标:
控制器级别
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>
请注意,我没有添加任何类型的错误处理。我也没有处理过任何超出常规指针的事情。此外,该函数确实需要对对象的引用,因为它正在对所述对象进行修改。最后,这段代码根本不涉及数组逻辑。不过,这可能正是您正在寻找的。p>