如何获得字段类型的零值

时间:2014-05-09 01:25:50

标签: go

我有一个包含许多字段的结构 - 我已经弄清楚如何使用反射提取字段名称,值和标记信息。我还想做的是确定字段的值是否与字段的默认值不同。

目前,我有这个(有效,但有点臭):

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

有没有一种优雅的方法来实现这个目标?

谢谢!

3 个答案:

答案 0 :(得分:25)

对于支持相等操作的类型,您只需比较保存零值和字段值的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()
}

答案 1 :(得分:6)

我无法发表评论,但如果您提供包含任何未导出字段的结构,则接受的答案会引起恐慌。我发现的技巧是检查是否可以设置字段 - 基本上忽略任何未导出的字段。

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
}

答案 2 :(得分:3)

您可以启用Kind()的{​​{1}}并使用相应的访问者(比类型少很多种)。类似的东西:

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 ) } 获取值的归零实例,但将其与valueField进行比较是不安全的,因为某些类型(例如切片和映射)不支持相等并且会发生混乱。