0

I'd like to iterate over the fields in a struct and prompt for string values to string fields, doing this recursively for fields that are pointers to structs.

Currently this is what I've tried, but I get an error when trying to set this value in the pointer's string field.

package main

import (
    "fmt"
    "reflect"
)

type Table struct {
    PK *Field
}

type Field struct {
    Name string
}


func main() {
    PopulateStruct(&Table{})
}

func PopulateStruct(a interface{}) interface {} {
    typeOf := reflect.TypeOf(a)
    valueOf := reflect.ValueOf(a)
    for i := 0; i < typeOf.Elem().NumField(); i++ {
        switch typeOf.Elem().Field(i).Type.Kind() {
        case reflect.String:
            fmt.Print(typeOf.Elem().Field(i).Name)
            var s string
            fmt.Scanf("%s", &s)
            valueOf.Elem().Field(i).SetString(s)
        case reflect.Ptr:
            ptr := reflect.New(valueOf.Elem().Field(i).Type())
            PopulateStruct(ptr.Elem().Interface())
            valueOf.Elem().Field(i).Set(ptr)
        }
    }
}

Expecting the return value to include an initialised struct with the pointers string field set.

Getting an error when setting the pointer's string field.

panic: reflect: call of reflect.Value.Field on zero Value

2
  • 1
    Is the pointer nil? If so, you'll need to initialize it with a value before traversing into it. Commented Aug 12, 2019 at 17:59
  • 1
    Update the question to show your test case. Commented Aug 12, 2019 at 18:33

1 Answer 1

3

I dropped your code as-is into the Go Playground and it doesn't build because PopulateStruct is declared as returning interface{} but does not actually return anything. Removing the declared return type produces the panic you mention.

This is because at entry to the outer PopulateStruct call, you have a valid pointer, pointing to a zero-valued Table. A zero-valued Table has one element: a nil pointer in it of type *Field. Your loop therefore runs once and finds a reflect.Ptr, as you expected. Adding more diagnostic print messages helps see what's happening:

fmt.Printf("PopulateStruct: I have typeOf=%v, valueOf=%v\n", typeOf, valueOf)
for i := 0; i < typeOf.Elem().NumField(); i++ {
    switch typeOf.Elem().Field(i).Type.Kind() {
// ... snipped some code ...
    case reflect.Ptr:
        ptr := reflect.New(valueOf.Elem().Field(i).Type())
        fmt.Println("after allocating ptr, we have:", ptr.Type(), ptr,
            "but its Elem is:", ptr.Elem().Type(), ptr.Elem())

This prints:

PopulateStruct: I have typeOf=*main.Table, valueOf=&{<nil>}
after allocating ptr, we have: **main.Field 0x40c138 but its Elem is: *main.Field <nil>

Given the way PopulateStruct itself is constructed, we must actually allocate a real Field instance now, before calling PopulateStruct. We can do this with:

        p2 := ptr.Elem()
        ptr.Elem().Set(reflect.New(p2.Type().Elem()))

(code borrowed from json.Unmarshal). Now we can fill in this Field, which has one field named Name of type String.

The overall strategy here is not that great, in my opinion: filling-in probably should take a generic pointer, not specifically a pointer-to-struct pointer. You can then emulate the indirect function in the json unmarshaller. However, the addition of these two lines—creating the target object and making the allocated pointer point to it—suffices to make your existing code run.

(Alternatively, you could just create and return a whole instance from scratch, in which case all you need is the type—but I'm assuming you have a pattern in which only some fields are nil.)

Here's the complete Go Playground example. I made a few other changes as there's nothing to scan from when using the playground.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.