omitempty tag does not work for empty time.Time struct field when using HSet
gh73962 opened this issue · 11 comments
Issue tracker is used for reporting bugs and discussing new features. Please use
stackoverflow for supporting issues.
Expected Behavior
type MyHash struct {
Key1 string `redis:"key1"`;
Key2 time.Time `redis:"key2,omitempty"`
}should be
hset myhash key1 value1
Current Behavior
hset myhash key1 value1 key2 0001-01-01T00:00:00Z
Possible Solution
https://github.com/redis/go-redis/blob/master/commands.go#L142
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Pointer:
return v.IsNil()
++++++++++++++++++++++++++++++++++++++++++++++
case reflect.Struct:
return v.IsZero()
++++++++++++++++++++++++++++++++++++++++++++++
}
return false
}Steps to Reproduce
type MyHash struct {
Key1 string `redis:"key1"`;
Key2 time.Time `redis:"key2,omitempty"`
}
data := MyHash{
Key1 : "value1",
Key2 : time.Time{},
}
if err := rdb.HSet(ctx, "key", model1).Err(); err != nil {
panic(err)
}Context (Environment)
Detailed Description
omitempty tag does not work for empty time.Time struct field when using HSet
Possible Implementation
If you agree with the proposal I made in the title "Possible Solution," I can proceed to complete this PR.
This solution makes sense to me, have you tried it? @gh73962
Also if you use *time.Time without omitempty tag, it would panic with nil pointer dereference. Is that the intended behavior?
This is the expected behaviour right now K guess but with go 1.24
We have omitzero which solves this directly
https://www.bytesizego.com/blog/go-124-omitzero#:~:text=What%20Does%20omitzero,nil%20(nothing)
Take a look at this
This is the expected behaviour right now K guess but with go 1.24 We have omitzero which solves this directly
https://www.bytesizego.com/blog/go-124-omitzero#:~:text=What%20Does%20omitzero,nil%20(nothing)
Take a look at this
I was checking the code, it seems like it doesn't respect when a struct field is nil or not, it just goes on and dereference it. This seems to be a bug for me, check following function for instance.
First of all unlike other types (bool, string, ...) it doesn't consider a *time.Time, second, it doesn't check whether a pointer is nil or not.
This solution makes sense to me, have you tried it?
In my use case, this solution is work. If you think your approach works, I need collect more test cases. However, directly checking case reflect.Struct feels a bit rough and not very elegant. I don’t have a better solution on my end either. @SoulPancake
@aliforever would you like to open a PR for the potential nil dereference that can occur in writeArgs ?
@gh73962 the proposed solution makes sense to me.
Upon a thorough examination, the function isEmptyValue(v reflect.Value) bool bears a resemblance to the functionality of standard library functions and can be utilized directly.But the default case of the func (v Value) IsZero() bool will panic.
https://github.com/golang/go/blob/go1.18/src/reflect/value.go#L1520
// IsZero reports whether v is the zero value for its type.
// It panics if the argument is invalid.
func (v Value) IsZero() bool {
The appropriate solution is. if omitEmpty(opt) && isEmptyValue(field) { -> if omitEmpty(opt) && field.IsZero() {
https://github.com/redis/go-redis/blob/master/commands.go#L104
// appendStructField appends the field and value held by the structure v to dst, and returns the appended dst.
func appendStructField(dst []interface{}, v reflect.Value) []interface{} {
typ := v.Type()
for i := 0; i < typ.NumField(); i++ {
tag := typ.Field(i).Tag.Get("redis")
if tag == "" || tag == "-" {
continue
}
name, opt, _ := strings.Cut(tag, ",")
if name == "" {
continue
}
field := v.Field(i)
// miss field
+ if omitEmpty(opt) && field.IsZero() {
continue
}
if field.CanInterface() {
dst = append(dst, name, field.Interface())
}
}
return dst
}
I will first submit a PR based on the solution initially proposed.
@aliforever would you like to open a PR for the potential
nildereference that can occur inwriteArgs?@gh73962 the proposed solution makes sense to me.
Sure, though @gh73962 has also said will open a PR
@gh73962 Will your solution fix the issue with nil pointers?
Upon a thorough examination, the function
isEmptyValue(v reflect.Value) boolbears a resemblance to the functionality of standard library functions and can be utilized directly.But the default case of thefunc (v Value) IsZero() boolwill panic.https://github.com/golang/go/blob/go1.18/src/reflect/value.go#L1520
// IsZero reports whether v is the zero value for its type. // It panics if the argument is invalid. func (v Value) IsZero() bool {The appropriate solution is.
if omitEmpty(opt) && isEmptyValue(field) {->if omitEmpty(opt) && field.IsZero() {https://github.com/redis/go-redis/blob/master/commands.go#L104// appendStructField appends the field and value held by the structure v to dst, and returns the appended dst. func appendStructField(dst []interface{}, v reflect.Value) []interface{} { typ := v.Type() for i := 0; i < typ.NumField(); i++ { tag := typ.Field(i).Tag.Get("redis") if tag == "" || tag == "-" { continue } name, opt, _ := strings.Cut(tag, ",") if name == "" { continue } field := v.Field(i) // miss field + if omitEmpty(opt) && field.IsZero() { continue } if field.CanInterface() { dst = append(dst, name, field.Interface()) } } return dst }I will first submit a PR based on the solution initially proposed.
I haven't tried to reproduce the issue I mentioned yet. I need to reproduce it first to identify the problem before handling it @aliforever