【问题标题】:How to get zero value of a field type如何获取字段类型的零值
【发布时间】:2019-02-02 18:28:23
【问题描述】:

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

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

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

有没有优雅的方法来完成这个?

谢谢!

【问题讨论】:

    标签: go


    【解决方案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()
    }
    

    【讨论】:

    • 切片、地图和其他不支持一般相等的类型会恐慌。如果你想处理这些类型,你需要别的东西。
    • 好点。我想你还需要递归检查这个来处理结构。
    • 也许你可以使用reflect.DeepEqual() 来处理这些类型
    【解决方案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
    }
    

    【讨论】:

    • reflect.Ptr的情况下,你可能需要先检查v.IsNil()
    【解决方案3】:

    您可以打开ValueKind() 并使用适当的访问器(种类比类型少得多)。比如:

    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()) 获得一个值的零实例,但将其与 valueField 进行比较是不安全的,因为某些类型(例如切片和映射)不支持相等并且会出现恐慌。

    【讨论】:

    • Go 默认情况下不会以这种方式遍历 case 语句。相反,您可以在每个 case 语句上放置多个逗号分隔值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多