1
0
mirror of https://github.com/redis/go-redis.git synced 2025-08-07 12:42:55 +03:00

Add redis.Scan() to scan results from redis maps into structs.

The package uses reflection to decode default types (int, string
etc.) from Redis map results (key-value pair sequences) into
struct fields where the fields are matched to Redis keys by tags.

Similar to how `encoding/json` allows custom decoders using
`UnmarshalJSON()`, the package supports decoding of arbitrary
types into struct fields by defining a `Decode(string) error`
function on types.

The field/type spec of every struct that's passed to Scan() is
cached in the package so that subsequent scans avoid iteration
and reflection of the struct's fields.
This commit is contained in:
Kailash Nadh
2021-01-28 10:58:24 +05:30
parent bf010a705d
commit 6f96bebac7
2 changed files with 243 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
package hscan
import (
"reflect"
"strings"
"sync"
)
// structField represents a single field in a target struct.
type structField struct {
index int
fn decoderFunc
}
// structFields contains the list of all fields in a target struct.
type structFields struct {
m map[string]*structField
}
// structMap contains the map of struct fields for target structs
// indexed by the struct type.
type structMap struct {
m sync.Map
}
func newStructMap() *structMap {
return &structMap{
m: sync.Map{},
}
}
func (s *structMap) get(t reflect.Type) (*structFields, bool) {
m, ok := s.m.Load(t)
if !ok {
return nil, ok
}
return m.(*structFields), true
}
func (s *structMap) set(t reflect.Type, sf *structFields) {
s.m.Store(t, sf)
}
func newStructFields() *structFields {
return &structFields{
m: make(map[string]*structField),
}
}
func (s *structFields) set(tag string, sf *structField) {
s.m[tag] = sf
}
func (s *structFields) get(tag string) (*structField, bool) {
f, ok := s.m[tag]
return f, ok
}
func makeStructSpecs(ob reflect.Value, fieldTag string) *structFields {
var (
num = ob.NumField()
out = newStructFields()
)
for i := 0; i < num; i++ {
f := ob.Field(i)
if !f.IsValid() || !f.CanSet() {
continue
}
tag := ob.Type().Field(i).Tag.Get(fieldTag)
if tag == "" || tag == "-" {
continue
}
tag = strings.Split(tag, ",")[0]
if tag == "" {
continue
}
// Use the built-in decoder.
out.set(tag, &structField{index: i, fn: decoders[f.Kind()]})
}
return out
}