如何解决Go中通过反射判断结构体属性是否为nil的异常问题?
我正在用Go开发一款应用,该应用需接收JSON并将其反序列化为不同的可能值。为此,我创建了一个父结构体,其中包含应用将接收的所有可能JSON格式对应的子结构体。我的思路是尝试将接收到的JSON反序列化到每个子结构体中,检查哪些子结构体未接收到任何值,后续将这类结构体设置为nil。
但问题在于,当我使用Go反射的isNil()方法时,它对通过反射创建的通用结构体无效,仅在已知结构体格式时有效。示例代码如下:
package main import ( "encoding/json" "fmt" "reflect" ) type User struct { Username *string `json:"username,omitempty" required:"true"` Password *string `json:"password,omitempty" required:"false"` } type UserError struct { Error *string `json:"error,omitempty" required:"true"` Code *int `json:"code,omitempty" required:"true"` } type UserOrError struct { User *User UserError *UserError } var userJson = `{ "username": "john@email.com", "password": "password" }` func main() { user := &UserOrError{} data := []byte(userJson) types := reflect.TypeOf(user).Elem() values := reflect.ValueOf(user).Elem() for i := 0; i < types.NumField(); i++ { fieldType := types.Field(i) unmarshalledValue := reflect.New(fieldType.Type) err := json.Unmarshal(data, unmarshalledValue.Interface()) if err != nil { panic(err) } value := unmarshalledValue.Elem() fmt.Printf("Unmarshalled: %+v - hasValues: %v \n", value, hasValues(value)) // Unmarshalled: &{Username:0x14000102110 Password:0x14000102120} - hasValues: true // Unmarshalled: &{Error:<nil> Code:<nil>} - hasValues: true values.Field(i).Set(value) } fmt.Printf("User: %+v - hasValues: %v\n", user.User, hasValues(user.User)) // User: &{Username:0x14000102110 Password:0x14000102120} - hasValues: true fmt.Printf("UserError: %+v - hasValues: %v\n", user.UserError, hasValues(user.UserError)) // UserError: &{Error:<nil> Code:<nil>} - hasValues: false } func hasValues(obj any) bool { var values reflect.Value if reflect.TypeOf(obj).Kind() == reflect.Ptr { values = reflect.ValueOf(obj).Elem() } else { values = reflect.ValueOf(obj) } for i := 0; i < values.NumField(); i++ { innerValue := values.Field(i) if innerValue.Kind() == reflect.Ptr && !innerValue.IsNil() { return true } else if !innerValue.IsZero() { return true } } return false }
可以看到,当预先知道结构体格式时,isNil()可正常工作,hasValues函数对UserError结构体返回false;但当传入通过reflect.New创建的通用结构体时,该函数始终返回true。
问题根源
你在循环中调用hasValues(value)时,传入的value是reflect.Value类型的实例,而不是实际的结构体指针。hasValues函数中reflect.TypeOf(obj).Kind()判断的是reflect.Value本身的类型,而非它包裹的实际对象类型。
reflect.Value本身是一个结构体类型,所以hasValues会进入else分支,直接对reflect.Value结构体的字段进行遍历,而不是去解析它内部存储的实际结构体(比如*UserError)。这就导致函数错误地认为存在有效值,返回true。
解决方法
调用hasValues时,需要将reflect.Value转换为实际的Go对象,通过value.Interface()方法即可实现:
修改main函数中的调用行:
fmt.Printf("Unmarshalled: %+v - hasValues: %v \n", value, hasValues(value.Interface()))
修改后,传入hasValues的是实际的结构体指针(比如*User或*UserError),函数就能正确判断结构体是否包含非空值了。
验证结果
修改后运行代码,输出会变为:
Unmarshalled: &{Username:0x140000a6020 Password:0x140000a6030} - hasValues: true Unmarshalled: &{Error:<nil> Code:<nil>} - hasValues: false User: &{Username:0x140000a6020 Password:0x140000a6030} - hasValues: true UserError: &{Error:<nil> Code:<nil>} - hasValues: false
这样就能正确区分反序列化后有有效值和无有效值的结构体了。
内容的提问来源于stack exchange,提问作者Felipe

