英文:
Recursively walk through nested structs
问题
我想构建一个方法,该方法以interface{}
形式接受一个结构体,并在所提供的结构体的任何字段为nil时返回true
。
以下是我目前的代码:
// ContainsNil 如果所提供的结构体中的任何字段为nil,则返回true。
//
// 如果提供的对象不是结构体,则该方法将引发panic。
// 嵌套结构体将递归检查。
// 不会深度检查映射和切片。这可能会改变。
func ContainsNil(obj interface{}) bool {
if obj == nil {
return true
}
s := reflect.Indirect(reflect.ValueOf(obj))
for i := 0; i < s.NumField(); i++ {
f := s.Type().Field(i)
field := s.Field(i)
if fieldIsExported(f) { // 导出字段的检查必须首先进行评估,以避免panic。
if field.Kind() == reflect.Struct {
if ContainsNil(field.Addr()) {
return true
}
} else {
if field.IsNil() {
return true
}
if field.Interface() == nil {
return true
}
}
}
}
return false
}
func fieldIsExported(field reflect.StructField) bool {
log.Println(field.Name)
return field.Name[0] >= 65 == true && field.Name[0] <= 90 == true
}
还有一个失败的测试:
func Test_ContainsNil_NilNestedValue_ReturnsTrue(t *testing.T) {
someNestedStruct := &c.SomeNestedStruct{
SomeStruct: c.SomeStruct{
SomeString: nil,
},
}
result := util.ContainsNil(someNestedStruct)
assert.True(t, result)
}
测试代码执行时没有引发panic,但失败了,因为该方法返回false
而不是true
。
我遇到的问题是我无法弄清楚如何正确地将嵌套结构体传递回递归调用的ContainsNil
方法中。
当为嵌套结构体进行递归调用时,fieldIsExported
方法返回false
,因为它没有接收到我期望的值。
我期望fieldIsExported
在第一次调用时接收到"SomeStruct",在第二次(递归)调用时接收到"SomeString"。第一次调用按预期进行,但在第二次调用时,fieldIsExported
接收到"typ",而我期望它接收到"SomeString"。
我已经进行了大量关于在结构体上使用反射的研究,但我还没有完全理解。有什么想法吗?
参考资料:
- https://stackoverflow.com/questions/25047424/pass-by-reference-nested-structures-through-reflection
- https://stackoverflow.com/questions/24333494/golang-reflection-on-embedded-structs
- https://golang.org/pkg/reflect/
- 大量的谷歌搜索
英文:
I want to build a method that takes a struct as an interface{}
and returns true
if any of the supplied struct's fields are nil.
Here's what I have at the moment:
// ContainsNil returns true if any fields within the supplied structure are nil.
//
// If the supplied object is not a struct, the method will panic.
// Nested structs are inspected recursively.
// Maps and slices are not inspected deeply. This may change.
func ContainsNil(obj interface{}) bool {
if obj == nil {
return true
}
s := reflect.Indirect(reflect.ValueOf(obj))
for i := 0; i < s.NumField(); i++ {
f := s.Type().Field(i)
field := s.Field(i)
if fieldIsExported(f) { // Exported-check must be evaluated first to avoid panic.
if field.Kind() == reflect.Struct {
if ContainsNil(field.Addr()) {
return true
}
} else {
if field.IsNil() {
return true
}
if field.Interface() == nil {
return true
}
}
}
}
return false
}
func fieldIsExported(field reflect.StructField) bool {
log.Println(field.Name)
return field.Name[0] >= 65 == true && field.Name[0] <= 90 == true
}
And a failing test:
func Test_ContainsNil_NilNestedValue_ReturnsTrue(t *testing.T) {
someNestedStruct := &c.SomeNestedStruct{
SomeStruct: c.SomeStruct{
SomeString: nil,
},
}
result := util.ContainsNil(someNestedStruct)
assert.True(t, result)
}
The test code executes without panicking, but fails because the method returns false
rather than true
.
The issue I'm having is that I can't figure out how to properly pass the nested struct back into the recursive call to ContainsNil
.
When recursive call is made for the nested structure, the fieldIsExported
method returns false
because it's not receiving the value that I would expect it to be receiving.
I expect fieldIsExported
to receive "SomeStruct" on its first call, and receive "SomeString" on the second (recursive) call. The first call goes as expected, but on the second call, fieldIsExported
receives "typ", when I would expect it to receive "SomeString".
I've done a bunch of research about using reflect on structs, but I haven't been able to get my head around this yet. Ideas?
References:
答案1
得分: 2
你检查当前字段是否为struct __value__
,但你从未考虑它是指向结构体或其他类型的reflect.Ptr
的情况,所以你的函数在这种情况下不会递归。以下是你的函数缺失的部分。
// ContainsNil函数会检查提供的结构体中是否有任何字段为nil。
//
// 如果提供的对象不是结构体,该方法将会引发panic。
// 嵌套结构体会被递归检查。
// 目前不会深度检查映射和切片,这可能会改变。
func ContainsNil(obj interface{}) bool {
if obj == nil {
return true
}
s := reflect.Indirect(reflect.ValueOf(obj))
for i := 0; i < s.NumField(); i++ {
f := s.Type().Field(i)
field := s.Field(i)
if fieldIsExported(f) { // 导出字段检查必须首先进行评估,以避免panic。
if field.Kind() == reflect.Ptr { // 指针或结构体指针的情况
if field.IsNil() {
return true
}
if ContainsNil(field.Interface()) {
return true
}
}
if field.Kind() == reflect.Struct {
if ContainsNil(field.Addr()) {
return true
}
} else {
if field.IsNil() {
return true
}
if field.Interface() == nil {
return true
}
}
}
}
return false
}
你可以在这里找到带有缺失部分的函数:https://play.golang.org/p/FdLxeee9UU
英文:
You check if the current field is a struct value, but you never account for the case when it is a reflect.Ptr
to a struct or something else, so your function never recurses for that case. Here is your function with the missing piece.
https://play.golang.org/p/FdLxeee9UU
// ContainsNil returns true if any fields within the supplied structure are nil.
//
// If the supplied object is not a struct, the method will panic.
// Nested structs are inspected recursively.
// Maps and slices are not inspected deeply. This may change.
func ContainsNil(obj interface{}) bool {
if obj == nil {
return true
}
s := reflect.Indirect(reflect.ValueOf(obj))
for i := 0; i < s.NumField(); i++ {
f := s.Type().Field(i)
field := s.Field(i)
if fieldIsExported(f) { // Exported-check must be evaluated first to avoid panic.
if field.Kind() == reflect.Ptr { // case when it's a pointer or struct pointer
if field.IsNil() {
return true
}
if ContainsNil(field.Interface()) {
return true
}
}
if field.Kind() == reflect.Struct {
if ContainsNil(field.Addr()) {
return true
}
} else {
if field.IsNil() {
return true
}
if field.Interface() == nil {
return true
}
}
}
}
return false
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论