skip to Main Content

I’m using redigo to store and retrieve data in redigo.
I have a struct that contains a type definition following time. I want to store the struct Data using HSET in Redis. I have a type definition to be able to use ScanStruct by adding a function RedisScan to my Timestamp type.

The problem is that Redis stores the Timestamp as ext, wall, loc following the time fields. You can’t create a new Time object from these fields so that’s fairly useless. What is the proper way to serialize a struct for redigo?

type Timestamp time.Time

func (t *Timestamp) RedisScan(x interface{}) error {
    ...
}

type Data struct {
    Timestamp  Timestamp `redis:"timestamp"`
}

func (r *RedisRepo) Write(data Data, key String) error {
    conn := r.pool.Get()
    defer conn.Close()
    conn.Send("HSET", redis.Args{key}.AddFlat(data)...)     
}

func (r *RedisRepo) Read(key string) (*Data, error) {
    var data Data
    conn := r.pool.Get()
    defer conn.Close()
    v, err := redis.Values(conn.Do("HGETALL", key))
    return redis.ScanStruct(v, &data)
}

2

Answers


  1. The redis.ScanStruct function and the Args.AddFlat method are missing features that make the pair usable as general purpose marshal/unmarshal functions.

    The approach for fixing the problem depends on what your goal is. ​
    See Save generic struct to redis if your goal is to load and save structs, not to access a Redis hash.

    If your goal is to access Redis hashes with defined names and values, then write code that translates between those definitions and Go values. Here’s an example for a hash that’s defined to have field "timestamp" with a value as decimal encoded Unix seconds:

    type Data struct {
        Timestamp time.Time
    }
    
    func (r *RedisRepo) Write(data Data, key string) error {
        conn := r.pool.Get()
        defer conn.Close()
        _, err := conn.Do("HSET", key, "timestamp", data.Timestamp.Unix())
        return err
    }
    
    func (r *RedisRepo) Read(key string) (*Data, error) {
        conn := r.pool.Get()
        defer conn.Close()
        v, err := redis.Values(conn.Do("HGETALL", key))
        if err != nil {
            return nil, err
        }
    
        var fields struct {
            Timestamp int64 `redis:"timestamp"`
        }
    
        err = redis.ScanStruct(v, &fields)
        if err != nil {
            return nil, err
        }
        return &Data{Timestamp: time.Unix(fields.Timestamp, 0)}, nil
    }
    

    Adjust the code as needed to match the Redis hash field definitions. Here’s the code for time in RFC 3339 format:

    type Data struct {
        Timestamp time.Time
    }
    
    func (r *RedisRepo) Write(data Data, key string) error {
        conn := r.pool.Get()
        defer conn.Close()
        _, err := conn.Do("HSET", key, "timestamp", data.Timestamp.Format(time.RFC3339))
        return err
    }
    
    func (r *RedisRepo) Read(key string) (*Data, error) {
        conn := r.pool.Get()
        defer conn.Close()
        v, err := redis.Values(conn.Do("HGETALL", key))
        if err != nil {
            return nil, err
        }
    
        var fields struct {
            Timestamp string `redis:"timestamp"`
        }
    
        err = redis.ScanStruct(v, &fields)
        if err != nil {
            return nil, err
        }
        t, err := time.Parse(time.RFC3339, fields.Timestamp)
        if err != nil {
            return nil, err
        }
        return &Data{Timestamp: t}, nil
    }
    

    The Read examples above are written so that the examples are easy to extend to multiple fields. If the application only needs to access a single field, replace the fields variable and ScanStruct nonsense with a call to redis.Int64(conn.Do("HGET", key, "timestamp") or redis.String(conn.Do("HGET", key, "timestamp")

    Login or Signup to reply.
  2. If you are going to add something into Hash Set of Redis you need to have these three values at least.

    1. A name to your hashset
    2. Keys for your hashset(can add extra keys later on as well)
    3. Values for your hashset

    Best way of coding this is to add a wrapper on the redigo library to abstract the implementation and inject the dependency into your code.

    // Not the best way
    func dummy(){
    
        redisConn := redisCache.pool.Get()
        defer redisConn.Close()
        response, err := redisConn.Do("HSET", args...)
    
    }
    
    type Implementation struct {
        RedisImplementation RedigoAbstractionConnection
    }
    
    // Best way
    func (i Implementation) AddDataToRedisHashSet(hashName string, key string, value string) error {
        var args = []interface{}{hashName}
        args = append(args, key, value)
        _, err := i.RedisImplementation.HSET(args...) // Returns an interface and an error
        if err != nil{
            // Handle Error
        }
        return nil
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search