dq62957
dq62957
2017-08-23 00:51

递归地遍历嵌套结构

已采纳

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条回答

  • doushifen4060 doushifen4060 4年前

    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
    }
    
    点赞 评论 复制链接分享

相关推荐