Как получить нулевое значение типа поля
У меня есть структура, содержащая много полей. Я выяснил, как извлекать информацию о имени поля, значении и теге, используя отражение. Я также хочу, чтобы определить, отличается ли значение поля от значения по умолчанию поля.
В настоящее время у меня есть (работает, но немного вонючий):
...
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 небезопасно, поскольку некоторые типы (например, срезы и карты) не поддерживают равенство и будут паниковать.