如何解决go-redis中HSet含指针字段结构体的序列化问题?
解决go-redis HSet存储带指针字段结构体的序列化问题
问题根源
go-redis的HSet在处理结构体字段时,仅支持实现了encoding.BinaryMarshaler或encoding.TextMarshaler接口的类型,而*int、*string这类原生指针类型并没有实现这些接口,因此触发序列化错误。
方案一:自定义包装类型实现Marshaler接口
Go不允许直接给原生类型(包括其指针)添加方法,因此可以通过自定义类型包装原生指针,再为包装类型实现序列化接口:
示例代码
import ( "encoding" "fmt" "strconv" ) // 包装*int的自定义类型 type IntPtr *int // 实现BinaryMarshaler接口 func (ip IntPtr) MarshalBinary() ([]byte, error) { if ip == nil { return []byte("null"), nil // 空指针可自定义序列化结果,比如返回空字节 } return []byte(strconv.Itoa(*ip)), nil } // 包装*string的自定义类型 type StringPtr *string // 实现BinaryMarshaler接口 func (sp StringPtr) MarshalBinary() ([]byte, error) { if sp == nil { return []byte("null"), nil } return []byte(*sp), nil } // 修改结构体字段类型为自定义包装类型 type pointer struct { PtrInt IntPtr `redis:"ptrInt"` PtrStr StringPtr `redis:"ptrStr"` } // 使用示例 i := 1 s := "s" p := pointer{IntPtr(&i), StringPtr(&s)} err := cl.HSet(context.Background(), "key", p).Err() if err != nil { fmt.Println(err) }
如果需要支持反序列化(如HGet操作),可继续实现encoding.BinaryUnmarshaler接口:
func (ip *IntPtr) UnmarshalBinary(data []byte) error { if string(data) == "null" { *ip = nil return nil } num, err := strconv.Atoi(string(data)) if err != nil { return err } *ip = IntPtr(&num) return nil } func (sp *StringPtr) UnmarshalBinary(data []byte) error { if string(data) == "null" { *sp = nil return nil } str := string(data) *sp = StringPtr(&str) return nil }
方案二:为结构体实现redis.Marshaler接口
无需修改字段类型,直接为结构体实现go-redis专属的序列化接口,手动处理指针字段的解引用:
import "github.com/go-redis/redis/v8" type pointer struct { PtrInt *int `redis:"ptrInt"` PtrStr *string `redis:"ptrStr"` } // 实现redis.Marshaler接口 func (p pointer) MarshalRedis() (interface{}, error) { result := make(map[string]interface{}) if p.PtrInt != nil { result["ptrInt"] = *p.PtrInt } else { result["ptrInt"] = nil // 空指针可选择不写入该字段,按需调整 } if p.PtrStr != nil { result["ptrStr"] = *p.PtrStr } else { result["ptrStr"] = nil } return result, nil }
HSet会自动调用该方法,将结构体转为redis可识别的map类型完成序列化。
方案三:反射工具批量处理指针字段
针对大量结构体的场景,封装通用反射函数,自动遍历并解引用结构体中的指针字段:
import ( "fmt" "reflect" ) // 解引用结构体指针字段,返回可直接用于HSet的map func dereferenceStructFields(v interface{}) (map[string]interface{}, error) { val := reflect.ValueOf(v) if val.Kind() != reflect.Struct { return nil, fmt.Errorf("输入必须为结构体,实际类型:%v", val.Kind()) } typ := val.Type() result := make(map[string]interface{}) for i := 0; i < val.NumField(); i++ { field := val.Field(i) fieldType := typ.Field(i) redisTag := fieldType.Tag.Get("redis") if redisTag == "" || redisTag == "-" { continue } if field.Kind() == reflect.Ptr { if field.IsNil() { result[redisTag] = nil } else { result[redisTag] = field.Elem().Interface() } } else { result[redisTag] = field.Interface() } } return result, nil } // 使用示例 processedMap, err := dereferenceStructFields(p) if err != nil { fmt.Println(err) } err = cl.HSet(context.Background(), "key", processedMap).Err()
这个工具函数可适配任意结构体,无需逐个修改定义或实现接口,适合批量处理场景。
内容的提问来源于stack exchange,提问作者schlumpel
相关产品推荐
相关产品推荐

