如何获取字段类型的零值

huangapple go评论97阅读模式
英文:

How to get zero value of a field type

问题

我有一个包含许多字段的结构体 - 我已经找到了使用反射提取字段名称、值和标签信息的方法。我还想确定字段的值是否与字段的默认值不同。

目前,我有以下代码(可以工作,但有点不太好):

...
qsMap := make(map[string]interface{})
var defaultTime time.Time
var defaultString string
...
// 获取字段名称和值
fieldName := s.Type().Field(i).Tag.Get("bson")
fieldValue := valueField.Interface()

// 使用反射确定字段的类型并应用适当的格式
switch fieldValue.(type) {
case time.Time:
    if fieldValue != defaultTime {
        qsMap[fieldName] = fieldValue
    }
case string:
    if fieldValue != defaultString {
        qsMap[fieldName] = fieldValue
    }
...
}

在我看来,应该有一种方法可以避免在这种情况下使用类型切换 - 我想要做的是构建一个字段/值的映射,其中值与它们的默认零值不同,类似于:

// 不起作用 -- 例如,如果类型为字符串的 fieldValue 与 "" 进行比较等。
if fieldValue != reflect.Zero(reflect.TypeOf(fieldValue)) {
    qsMap[fieldName] = fieldValue
}

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

谢谢!

英文:

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!

答案1

得分: 27

对于支持相等操作的类型,您可以直接比较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
    }
    // 直接比较其他类型:
    z := reflect.Zero(v.Type())
    return v.Interface() == z.Interface()
}

希望对您有所帮助!

英文:

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 &lt; v.Len(); i++ {
			z = z &amp;&amp; isZero(v.Index(i))
		}
		return z
	case reflect.Struct:
		z := true
		for i := 0; i &lt; v.NumField(); i++ {
			z = z &amp;&amp; isZero(v.Field(i))
		}
		return z
	}
	// Compare other types directly:
	z := reflect.Zero(v.Type())
	return v.Interface() == z.Interface()
}

答案2

得分: 7

我无法发表评论,但是如果你提供一个带有任何未导出字段的结构体,接受的答案会出现错误。我找到的技巧是检查字段是否可以设置 - 实质上忽略任何未导出的字段。

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))
    }
    // 直接比较其他类型:
    z := reflect.Zero(v.Type())
    result := v.Interface() == z.Interface()

    return result
}

希望对你有帮助!

英文:

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 &lt; v.Len(); i++ {
			z = z &amp;&amp; isZero(v.Index(i))
		}
		return z
	case reflect.Struct:
		z := true
		for i := 0; i &lt; v.NumField(); i++ {
			if v.Field(i).CanSet() {
				z = z &amp;&amp; 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

得分: 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() {...}
//添加更多的情况,如Float、Bool、String等(以及在http://golang.org/pkg/reflect/#Kind中列出的其他任何类型)
}

你也可以使用reflect.Zero(valueField.Type())来获取一个零值实例,但与valueField进行比较是不安全的,因为某些类型(如切片和映射)不支持相等性比较,会导致恐慌。

英文:

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.

答案4

得分: 3

只需使用以下代码:

reflect.Value.IsZero()

这个方法是在Go 1.13中引入的,发布于2019年9月3日(该问题持续了5年)。

(我也错过了它,所以我首先来到这里。)

英文:

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

huangapple
  • 本文由 发表于 2014年5月9日 09:25:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/23555241.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定