go-redis HSet() struct with pointer fields

522 views Asked by At

I'm trying to HSet() structs with pointers.

Example:

cl := redis.NewClient(&redis.Options{
    Addr:     "localhost:6386",
    Password: "", // no password set
    DB:       0,  // use default DB
    PoolSize: 100,
})

type pointer struct {
    PtrInt *int    `redis:"ptrInt"`
    PtrStr *string `redis:"ptrStr"`
}

i := 1
s := "s"
p := pointer{&i, &s}

err := cl.HSet(context.Background(), "key", p).Err()
fmt.Println(err)

// redis: can't marshal *int (implement encoding.BinaryMarshaler)

How would you solve this? For some reason, the pointers are not dereferenced, thus I have to save like 30 different structs.

Thank you!

Edit: As pointed out in the comments, it seems like I have to implement the encoding.BinaryMarshaler interface for pointers to simple data types like *int and *string. How would you do that?

So far, my solution is:

func main() {
    cl := redis.NewClient(&redis.Options{
        Addr:     "localhost:6386",
        Password: "", // no password set
        DB:       0,  // use default DB
        PoolSize: 100,
    })

    type pointer struct {
        PtrInt *int    `redis:"ptrInt"`
        I      int     `redis:"int"`
        PtrStr *string `redis:"ptrStr"`
    }

    i := 1
    s := "s"
    p := pointer{&i, 1, &s}

    typ := reflect.TypeOf(p)
    val := reflect.ValueOf(p)

    for i := 0; i < typ.NumField(); i++ {

        // set only, if it's not a pointer
        if val.Field(i).Kind() != reflect.Ptr {
            err := cl.HSet(context.Background(), "test:1", typ.Field(i).Name, val.Field(i).Interface()).Err()
            if err != nil {
                t.Errorf("%s", err.Error())
            }
        }
    }
}

1

There are 1 answers

5
meshkati On

go-redis is not handling struct fields so good and somewhere in it's tutorial it said:

Because go-redis does not provide a helper to save structs in Redis, we are using a pipeline to load some data into our database.

You can try and implement the encoding.BinaryMarshaler for your type pointer and other types, but it is also time-consuming if you have so many structs with pinter fields.

My recommendation is to forget about struct feature of the redisClient.Hset() and try to use map[string]interface{}, so you have to convert your structs into map[string]interface{}. Hopefully this is implemented in many encoding libraries, like json or mapstructure. For your case, something like this works:

package main

import (
    "context"
    "encoding/json"

    "github.com/redis/go-redis/v9"
)

type pointer struct {
    PtrInt *int
    PtrStr *string
}

func main() {
    ctx := context.Background()
    cl := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
        PoolSize: 100,
    })

    i := 1
    s := "s"
    p := pointer{&i, &s}

    data, err := json.Marshal(p)
    if err != nil {
        panic(err)
    }
    var inInterface map[string]interface{}
    err = json.Unmarshal(data, &inInterface)
    if err != nil {
        panic(err)
    }

    err = cl.HSet(ctx, "key", inInterface).Err()
    if err != nil {
        panic(err)
    }
}