Skip to content

Instantly share code, notes, and snippets.

@knadh
Last active June 2, 2025 20:58
Show Gist options
  • Select an option

  • Save knadh/63862e5713c7ed296e0a14b888ca86aa to your computer and use it in GitHub Desktop.

Select an option

Save knadh/63862e5713c7ed296e0a14b888ca86aa to your computer and use it in GitHub Desktop.

Revisions

  1. knadh revised this gist Jan 24, 2021. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions go-redis-struct-scan.go
    Original file line number Diff line number Diff line change
    @@ -75,8 +75,6 @@ func getField(ob reflect.Value, name, fieldTag string) reflect.Value {
    continue
    }

    fmt.Println(f.Type().PkgPath())

    tag = strings.Split(tag, ",")[0]
    if tag == name {
    return f
  2. knadh created this gist Jan 18, 2021.
    134 changes: 134 additions & 0 deletions go-redis-struct-scan.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,134 @@
    package cacheman

    import (
    "errors"
    "fmt"
    "reflect"
    "strconv"
    "strings"
    )

    // ScanToStruct takes a []interface{} result from a redis.SliceCmd{} result
    // and scans the keys and values from the result into a given struct by its
    // field tags.
    //
    // Example:
    // Scan a {name, age} map from an HGETALL call to a struct.
    // type Test struct {
    // Name string `redis:"name"`
    // Age int `redis:"age"`
    // }

    // cmd := redis.NewSliceCmd(p.c.ctx, "HGETALL", "test")
    // client.Process(ctx, cmd)

    // var out Test
    // ScanToStruct(cmd.Val(), &out, "redis")
    // fmt.Println(out)
    //
    func ScanToStruct(vals []interface{}, obj interface{}, fieldTag string) error {
    if len(vals)%2 != 0 {
    return errors.New("args should have an even number of items (key-val)")
    }

    ob := reflect.ValueOf(obj)
    if ob.Kind() == reflect.Ptr {
    ob = ob.Elem()
    }

    if ob.Kind() != reflect.Struct {
    return fmt.Errorf("failed to decode form values to struct, received non struct type: %T", ob)
    }

    for i := 0; i < len(vals)/2; i++ {
    key, ok := vals[i*2].(string)
    if !ok {
    continue
    }

    val, ok := vals[(i*2)+1].(string)
    if !ok {
    continue
    }

    // Get the field from the struct with the matching key.
    f := getField(ob, key, fieldTag)
    if !f.IsValid() {
    continue
    }

    // Convert the string value from Redis and set it on the struct field.
    if _, err := setVal(f, val); err != nil {
    return fmt.Errorf("failed to decode `%v`, got: `%s` (%v)", key, val, err)
    }
    }

    return nil
    }

    func getField(ob reflect.Value, name, fieldTag string) reflect.Value {
    for i := 0; i < ob.NumField(); i++ {
    f := ob.Field(i)
    if f.IsValid() && f.CanSet() {
    tag := ob.Type().Field(i).Tag.Get(fieldTag)
    if tag == "" || tag == "-" {
    continue
    }

    fmt.Println(f.Type().PkgPath())

    tag = strings.Split(tag, ",")[0]
    if tag == name {
    return f
    }
    }
    }

    return reflect.Value{}
    }

    func setVal(f reflect.Value, val string) (bool, error) {
    switch f.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
    v, err := strconv.ParseInt(val, 10, 0)
    if err != nil {
    return false, fmt.Errorf("expected int")
    }
    f.SetInt(v)

    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
    v, err := strconv.ParseUint(val, 10, 0)
    if err != nil {
    return false, fmt.Errorf("expected unsigned int")
    }
    f.SetUint(v)

    case reflect.Float32, reflect.Float64:
    v, err := strconv.ParseFloat(val, 0)
    if err != nil {
    return false, fmt.Errorf("expected decimal")
    }
    f.SetFloat(v)

    case reflect.String:
    f.SetString(val)

    case reflect.Slice:
    // []byte slice ([]uint8).
    if f.Type().Elem().Kind() == reflect.Uint8 {
    f.SetBytes([]byte(val))
    }

    case reflect.Bool:
    b, err := strconv.ParseBool(val)
    if err != nil {
    return false, fmt.Errorf("expected boolean")
    }
    f.SetBool(b)

    default:
    return false, nil
    }

    return true, nil
    }