Reputation: 1853
I wanted a way to selectively copy Go fields from one struct to another. This allows me to update data from one struct to another without changing certain information. Here's the solution I came up with. It depends on setting a tag field of "update". Would appreciate any feedback for making this more robust or better or maybe why this is just a bad idea to begin with.
import (
"errors"
"fmt"
"reflect"
)
func UpdateStruct(src, dst interface{}) error {
if reflect.TypeOf(src) != reflect.TypeOf(dst) {
return errors.New("structs not of same type")
}
if reflect.ValueOf(src).Kind() != reflect.Ptr {
return errors.New("arguments must be pointers")
}
srcVal := reflect.ValueOf(src).Elem()
srcType := srcVal.Type()
dstVal := reflect.ValueOf(dst).Elem()
for i := 0; i < srcVal.NumField(); i++ {
s := srcType.Field(i)
if tag := s.Tag.Get("update"); tag == "" {
continue
}
fieldName := srcType.Field(i).Name
d := dstVal.FieldByName(fieldName)
if d.IsValid() {
if d.CanSet() {
d.Set(srcVal.Field(i))
} else {
return fmt.Errorf("cannot set field: %s", fieldName)
}
} else {
return fmt.Errorf("invalid field: %s", fieldName)
}
}
return nil
}
example struct:
type Tester struct {
ID string
Name string `update:"true"`
Date time.Time `update:"true"`
Decimal float64 `update:"true"`
Number int `update:"true"`
CaseID uuid.UUID `update:"true"`
}
Upvotes: 1
Views: 575
Reputation: 121089
The code will panic if the arguments are not pointers to structs. There'a check for a pointer, but not for a pointer to a struct. Add this code:
srcVal := reflect.ValueOf(src).Elem()
if srcVal.Kind() != reflect.Struct {
return errors.New("arguments must be pointers to structs")
}
Because the function is starting with a pointer to a struct, the fields are guaranteed to be settable. Because the field values are obtained from a valid struct value, the fields are guaranteed to be valid. Fields can be accessed by index in addition to by name. Given this, the inner loop can be simplified to:
for i := 0; i < srcVal.NumField(); i++ {
s := srcType.Field(i)
if tag := s.Tag.Get("update"); tag == "" {
continue
}
dstVal.Field(i).Set(srcVal.Field(i))
}
Upvotes: 3