Pergunta

I have a struct containing many fields - I've figured out how to extract the field name, value, and tag information using reflection. What I also want to do is to determine if the value of a field is different from the field's default value.

Currently, I have this (works, but a bit smelly):

...
qsMap := make(map[string]interface{})
var defaultTime time.Time
var defaultString string
...
// get the field name and value
fieldName := s.Type().Field(i).Tag.Get("bson")
fieldValue := valueField.Interface()

// use reflection to determine the TYPE of the field and apply the proper formatting
switch fieldValue.(type) {
case time.Time:
if fieldValue != defaultTime {
    qsMap[fieldName] = fieldValue
}
case string:
if fieldValue != defaultString {
    qsMap[fieldName] = fieldValue
}
...
}

Seems to me that there should be a way to avoid the type switch in this case - what I'm trying to do is build up a map of field/values that have a value different from their default zero value, something like:

// doesn't work -- i.e., if fieldValue of type string would be compared against "", etc.
if fieldValue != reflect.Zero(reflect.Type(fieldValue)) {
    qsMap[fieldName] = fieldValue
}

Is there an elegant way to accomplish this?

Thanks!

Foi útil?

Solução

For types that support the equality operation, you can just compare interface{} variables holding the zero value and field value. Something like this:

v.Interface() == reflect.Zero(v.Type()).Interface()

For functions, maps and slices though, this comparison will fail, so we still need to include some special casing. Further more, while arrays and structs are comparable, the comparison will fail if they contain non-comparable types. So you probably need something like:

func isZero(v reflect.Value) bool {
    switch v.Kind() {
    case reflect.Func, reflect.Map, reflect.Slice:
        return v.IsNil()
    case reflect.Array:
        z := true
        for i := 0; i < v.Len(); i++ {
            z = z && isZero(v.Index(i))
        }
        return z
    case reflect.Struct:
        z := true
        for i := 0; i < v.NumField(); i++ {
            z = z && isZero(v.Field(i))
        }
        return z
    }
    // Compare other types directly:
    z := reflect.Zero(v.Type())
    return v.Interface() == z.Interface()
}

Outras dicas

I couldn't post a comment, but the accepted answer panics if you provide a struct with any unexported fields. The trick I've found is to check if the field can be set - essentially ignoring any unexported fields.

func isZero(v reflect.Value) bool {
    switch v.Kind() {
    case reflect.Func, reflect.Map, reflect.Slice:
        return v.IsNil()
    case reflect.Array:
        z := true
        for i := 0; i < v.Len(); i++ {
            z = z && isZero(v.Index(i))
        }
        return z
    case reflect.Struct:
        z := true
        for i := 0; i < v.NumField(); i++ {
            if v.Field(i).CanSet() {
                z = z && isZero(v.Field(i))
            }
        }
        return z
    case reflect.Ptr:
        return isZero(reflect.Indirect(v))
    }
    // Compare other types directly:
    z := reflect.Zero(v.Type())
    result := v.Interface() == z.Interface()

    return result
}

You can switch on the Kind() of the Value and use the appropriate accessor (many fewer kinds than types). Something like:

switch valueField.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    if valueField.Int() == 0 {...}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
    if valueField.Uint() == 0 {...}
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
    if valueField.IsNil() {...}
//add more cases for Float, Bool, String, etc (and anything else listed http://golang.org/pkg/reflect/#Kind )
}

You could also get a zeroed instance of a value by using reflect.Zero(valueField.Type()) but it is not safe to compare that with valueField since some types (such as slices and maps) do not support equality and would panic.

Simply use

reflect.Value.IsZero()

which is introduced in Go 1.13, released 2019-09-03 (the issue lasted 5 years).

(Missed it too, so I first landed here.)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top