jinzhu / copier

Copier for golang, copy value from struct to struct and more
MIT License
5.58k stars 489 forks source link

Null sql.Null* values should be copied as nil pointer destination #165

Closed kcchu closed 2 years ago

kcchu commented 2 years ago

Reproducible Example

https://go.dev/play/p/FBJR9wgiYgf

    var (
        from struct {
            S sql.NullString
            T sql.NullTime
        }
        to struct {
            S *string
            T *time.Time
        }
    )
    err := copier.Copy(&to, from)
    if err != nil {
        fmt.Printf("err should be nil but %v\n", err)
    }
    if to.S != nil {
        fmt.Printf("to.S should be nil but %v\n", to.S)
    }
    if to.T != nil {
        fmt.Printf("to.T should be nil but %v\n", to.T)
    }

Actual output:

to.S should be nil but 0xc000014350
to.T should be nil but 0001-01-01 00:00:00 +0000 UTC

Description

When a sql.Null* field (or other driver.Valuer types) is copied to a pointer type field (e.g. from sql.NullString to *string), the destination is always initialized to the zero value of the pointed type (e.g. a pointer to empty string), even if sql.Null* field is invalid (which represent NULL value in sql).

It is expected that invalid sql.Null* value should be copied to the pointer type destination field as nil.

The bug occurred in the following fragment:


                    // process for nested anonymous field
                    destFieldNotSet := false
                    if f, ok := dest.Type().FieldByName(destFieldName); ok {
                        for idx := range f.Index {
                            destField := dest.FieldByIndex(f.Index[:idx+1])

                            if destField.Kind() != reflect.Ptr {
                                continue
                            }

                            if !destField.IsNil() {
                                continue
                            }
                            if !destField.CanSet() {
                                destFieldNotSet = true
                                break
                            }

                            // destField is a nil pointer that can be set
                            newValue := reflect.New(destField.Type().Elem())
                            destField.Set(newValue)
                        }
                    }

The above fragment attempted to initialize the struct for embedded fields, but it resulted in all pointer field to be initialised to its zero value even if it is not an embedded struct.