1

I know you can create a struct with a literal, listing the fields in order:

type Foo struct {
    A string 
    B string
    C string 
}

foo := Foo{ "foo", "bar", "baz" }

Is there any way to do the same thing dynamically? I have an array of values (actually an array of arrays) and I want to assign them to an array of structs in field order, and there are rather more than three fields. Is there a way to say "assign this struct's fields from this array of values in order"? I really don't want to write a bunch of structArray[i].field1 = dataArray[i][0]; structArray[i].field2 = dataArray[i][1], etc.

My thoughts so far have been to use reflect, which seems overkillish, or maybe to create an array of field names and build json strings and unmarshal them. Any better ideas?

3
  • 3
    In order to dynamically assign fields, you need to use reflect. Using json is just indirectly using reflect for the same thing. If this is primarily used around serialization, why not use a map? Commented Nov 7, 2022 at 15:45
  • In this case the struct is required by a module interface, not just one I've made up. Otherwise a map would work better, yes, since I could pair up keys from my array of field names with values without having to go through json/reflect. Commented Nov 7, 2022 at 16:01
  • 3
    Using reflect is not overkillish if what you want is to dynamically assign fields in a language that's statically typed. Commented Nov 7, 2022 at 16:32

1 Answer 1

4

With reflection you can write a function like this:

func populate(dst any, src any) {
    v := reflect.ValueOf(dst)
    if v.Type().Kind() != reflect.Pointer {
        panic("dst must be a pointer")
    }
    v = v.Elem()
    if v.Type().Kind() != reflect.Struct {
        panic("dst must be a pointer to struct")
    } 

    w := reflect.ValueOf(src)
    if w.Type().Kind() != reflect.Slice {
        panic("src must be a slice")
    }
    for i := 0; i < v.NumField(); i++ {
        // in case you need to support source slices of arbitrary types
        value := w.Index(i)
        if value.Type().Kind() == reflect.Interface {
            value = value.Elem()
        }
        v.Field(i).Set(value)
    }
}

You must make sure that dst is addressable, hence pass a pointer to Foo into populate; and that the i-th element in the source slice is actually assignable to the corresponding i-th field in the struct.

The code above is in a quite simplified form. You can add additional checks to it, e.g. with CanAddr or AssignableTo, if you think callers may misbehave.

Call it like:

func main() {
    f := Foo{}
    populate(&f, []string{"foo", "bar", "baz"})
    fmt.Println(f) // {foo bar baz}
}

Here's a playground that also shows that you can pass a slice of []any as the source slice, in case the struct fields aren't all the same type: https://go.dev/play/p/G8qjDCt79C7

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.