JonLim
JonLim

Reputation: 1413

Traverse struct with reflect, create new slice of pointer type with reflect

Context: I'm trying to take any struct, and fill it with random data.

My big sticking point at the moment is that if a struct has a field that is a slice with a pointer type (ie. []*Foo), I'm unable to figure out how to create data for that struct using reflection.

Here's what my function currently looks like:

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
}

This works great on a slice without pointer types, but here's an example of some structs that it has trouble with:

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

type Child struct {
    Name string
    Age  int
}

Where if I created a Parent{} and passed it into randFill(Parent{}), it has trouble generating values for *Child because when it gets to this line:

newC := slice.Index(j)

The value of newC at this point, when handling the slice of []*Child is (*Child)(nil), and it's a reflect.Value type.

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

There's something I'm missing around being able to initialize a pointer type, from a reflect.Value, or I've created the incorrect type of slice, and that is the root of my problems?

Upvotes: 0

Views: 1046

Answers (2)

user12258482
user12258482

Reputation:

A simple approach is to write a function that fills a reflect.Value with random data. The function calls itself recursively for structured values (slices, structs, ...).

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.

There are a couple of simplifications in this code compared to the code in the question. The first is that this answer avoids the reflect.ValueOf and .Interface() calls by working with reflect.Value. The second is that pointers are handled as a top-level case, thus eliminating the need for pointer related code in the slice element and field code.

Upvotes: 1

Burak Serdar
Burak Serdar

Reputation: 51577

When you find put that the element is a pointer, you have to create an instance of the type the pointer points to, but you're creating a new pointer. Try this:

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

Upvotes: 1

Related Questions