Sebastian
Sebastian

Reputation: 1853

Selectively copy go struct fields

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

Answers (1)

Thundercat
Thundercat

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))
}

Run it on the playground

Upvotes: 3

Related Questions