Hi @vmihailenco,
Here's a simple solution that scans the results of type []interface{} from NewSliceCmd and NewStringSliceCmd to a struct using reflection. The comment on the function shows example usage. Useful for scanning the results of HMGET, HGETALL etc. directly into a struct.
What do you think of merging this feature into the lib in one of the following ways? If yes, I can send a PR.
goredis.ScanToStruct(vals, target)utils.ScanToStruct(vals, target)SliceCmd.ScanToStruct(target)Related: https://github.com/go-redis/redis/issues/672 https://github.com/go-redis/redis/issues/348 https://github.com/go-redis/redis/issues/970 https://github.com/go-redis/redis/issues/16
Hi @knadh,
Two obvious things that are missing are tests and docs/examples. I won't insist much on docs, but tests are mandatory.
And I guess code requires more work too and I don't have much time to spend on this. I would say that this issue requires 10-20 hours of work to finish. Do you have that time? :)
@vmihailenco the gist is just a proof of concept. Of course, it'll need tests + examples before it can go into the lib.
And I guess code requires more work too
Could you elaborate on what exactly?
Could you elaborate on what exactly?
Take this only as suggestions since I have not spend much time on this but I have some experience with https://github.com/vmihailenco/msgpack.
I think it makes sense to start with with decoder func. So for each type you have a func that scans a string into a value:
type decoderFunc func(v reflect.Value, s string) error
var valueDecoders = []decoderFunc{
reflect.Bool: decodeBoolValue,
reflect.Int: decodeInt64Value,
..
}
func decodeBoolValue(v reflect.Value, s string) error {
b, err := strconv.ParseBool(val)
if err != nil {
return fmt.Errorf("expected boolean")
}
f.SetBool(b)
return nil
}
Then for each struct you can construct a map of decoders once and reuse it:
type field struct {
name string // field name
decoder decoderFunc
}
type structInfo struct {
typ reflect.Type // struct type
m map[string]*field
}
func (s *structInfo) Decode(v reflect.Value, key, value string) error {
return s.m[key].decoder(v, value)
}
Then your function will look like this:
func Scan(vals interface{}, dst interface{}) error {
v := reflect.ValueOf(v)
s := descStruct(v.Type())
switch vals := vals.(type) {
case []string:
for i := 0; i < len(vals); i += 2 {
if err := s.Decode(v, vals[i], vals[i+1]); err != nil {
return err
}
}
return nil
case []interface{}:
// todo
}
}
This way:
[]interface{} and []stringDoes that make sense?
Yup, that makes sense. I will try and find some time to prototype this.
@vmihailenco, here's a prototype with a working example.
encoding/json does with UnmarshalJSON(). You can define a Decode() on any type, not just structs. eg:type Tag string
func (t *Tag) Decode(s string) error {
*t = "<<" + Tag(s) + ">>"
return nil
}
type CSV struct {
Vals []string
}
func (c *CSV) Decode(s string) error {
c.Vals = strings.Split(s, ",")
return nil
}
type Record struct {
Name string `redis:"name"`
Number int `redis:"number"`
Tag Tag `redis:"tag"`
Items CSV `redis:"items"`
Empty bool `redis:"-"`
}
var r Record
Scan(results, &r, "redis")
[]interface{} and []string as all slice/map commands seem to produce []interface{} as the result? Won't []interface{} do?If you think the approach makes sense, I can add tests and send a PR. Thanks.
:+1:
@knadh looks good - please send a PR (before writing tests). I have few comments but it is easier to make them on a PR.
Is it necessary to support both []interface{} and []string as all slice/map commands seem to produce []interface{} as the result? Won't []interface{} do?
No need to support []string then.
Sent a PR: https://github.com/go-redis/redis/pull/1631
https://github.com/go-redis/redis/pull/1631 is merged! Thanks @vmihailenco.
Most helpful comment
@vmihailenco, here's a prototype with a working example.
encoding/jsondoes withUnmarshalJSON(). You can define aDecode()on any type, not just structs. eg:[]interface{}and[]stringas all slice/map commands seem to produce[]interface{}as the result? Won't[]interface{}do?If you think the approach makes sense, I can add tests and send a PR. Thanks.