使用反射遍历结构,使用反射创建新的指针类型切片

时间:2020-01-11 16:23:55

标签: go

上下文:我正在尝试获取任何struct,并用随机数据填充它。

此刻,我最大的障碍是,如果struct的字段是具有指针类型的切片(即[]*Foo),我将无法弄清楚如何创建反射获取该结构的数据。

这是我目前的功能:

func randFill(in interface{}) interface{} {
    t := reflect.TypeOf(in)
    v := reflect.ValueOf(in)

    if v.Kind() == reflect.Ptr {
        v = v.Elem()
    }

    switch v.Kind() {
    case reflect.Struct:
        newStr := reflect.New(t).Elem()
        for i := 0; i < t.NumField(); i++ {
            newV := randFill(reflect.New(t.Field(i).Type).Interface())
            newStr.Field(i).Set(reflect.ValueOf(newV))
        }

        return newStr.Interface()
    case reflect.Slice:
        num := rand.Intn(10)
        slice := reflect.MakeSlice(v.Type(), num, num)

        for j := 0; j < num; j++ {
            newC := slice.Index(j)
            if newC.Kind() == reflect.Ptr {
                ncInt := reflect.New(newC.Type())
                newC = ncInt.Elem()
            }
            gen := randFill(newC.Interface())
            slice.Index(j).Set(reflect.ValueOf(gen))
        }

        return slice.Interface()
    //
    // ... there are other cases down here for handling primitives
    //
    }

    return nil
}

这在没有指针类型的切片上效果很好,但是下面是一些结构有问题的示例:

type Parent struct {
    Name     string
    Age      int
    Children []*Child
}

type Child struct {
    Name string
    Age  int
}

如果我创建了一个Parent{}并将其传递给randFill(Parent{}),那么在为*Child生成值时会遇到麻烦,因为到达此行时:

newC := slice.Index(j)

此时,当处理newC的切片时,[]*Child的值为(*Child)(nil),它是reflect.Value类型。

newC := slice.Index(j)
fmt.Printf("%#v, %T", newC, newC) // Outputs: (*Child)(nil), reflect.Value

我无法从reflect.Value初始化指针类型,或者我创建的切片类型不正确,这就是问题的根源吗?

2 个答案:

答案 0 :(得分:1)

当发现放置元素是一个指针时,必须创建该指针指向的类型的实例,但是您正在创建一个新的指针。试试这个:

if newC.Kind() == reflect.Ptr {
    ncInt := reflect.New(newC.Type().Elem())
    newC = ncInt.Elem()
}

答案 1 :(得分:1)

一种简单的方法是编写一个用随机数据填充reflect.Value的函数。该函数以递归方式调用结构化值(切片,结构等)。

func randFillValue(v reflect.Value) {
    switch v.Kind() {
    case reflect.Ptr:
        v.Set(reflect.New(v.Type().Elem()))
        randFillValue(v.Elem())
    case reflect.Struct:
        for i := 0; i < v.NumField(); i++ {
            randFillValue(v.Field(i))
        }
    case reflect.Slice:
        num := rand.Intn(10)
        v.Set(reflect.MakeSlice(v.Type(), num, num))
        for i := 0; i < num; i++ {
            randFillValue(v.Index(i))
        }
    case reflect.Int:
        v.SetInt(10)  // TODO: fill with random int
    case reflect.String:
        v.SetString("random string") // TODO: fill with random string
    }
    // TODO: add other reflect.Kind
}

// randFill fills the value pointed to pv with random values.
func randFill(pv interface{}) {
    randFillValue(reflect.ValueOf(pv).Elem())
}

Run it on the playground

与问题中的代码相比,此代码有两个简化。首先是该答案通过与reflect.ValueOf一起使用避免了.Interface()reflect.Value的调用。第二个问题是将指针作为顶级案例处理,从而消除了slice元素和域代码中与指针相关的代码的需要。