Как получить нулевое значение типа поля

У меня есть структура, содержащая много полей. Я выяснил, как извлекать информацию о имени поля, значении и теге, используя отражение. Я также хочу, чтобы определить, отличается ли значение поля от значения по умолчанию поля.

В настоящее время у меня есть (работает, но немного вонючий):

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

Мне кажется, что в этом случае должен быть способ избежать переключения типа - то, что я пытаюсь сделать, это создать карту поля/значений, значение которой отличается от нулевого значения по умолчанию, что-то вроде

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

Есть ли элегантный способ сделать это?

Спасибо!

Ответы

Ответ 1

Для типов, поддерживающих операцию равенства, вы можете просто сравнить переменные interface{}, содержащие нулевое значение и значение поля. Что-то вроде этого:

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

Для функций, карт и срезов, это сравнение не удастся, поэтому нам еще нужно включить какой-то специальный корпус. Кроме того, в то время как массивы и структуры сопоставимы, сравнение не удастся, если они содержат несопоставимые типы. Поэтому вам, вероятно, понадобится что-то вроде:

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

Ответ 2

Я не могу опубликовать комментарий, но принятый ответ паникует, если вы предоставляете структуру с любыми нераспределенными полями. Трюк, который я нашел, - проверить, можно ли установить поле - по существу игнорируя любые нераспределенные поля.

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
}

Ответ 3

Вы можете включить Kind() в Value и использовать соответствующий аксессор (несколько видов, чем типы). Что-то вроде:

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

Вы также можете получить нулевой экземпляр значения с помощью reflect.Zero(valueField.Type()), но сравнивать его со значениемFieldField небезопасно, поскольку некоторые типы (например, срезы и карты) не поддерживают равенство и будут паниковать.