3

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)
}
Richard Wilson
  • 297
  • 4
  • 17
Oliver Becher
  • 33
  • 1
  • 4

2 Answers2

2

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")

Charlie Tumahai
  • 113,709
  • 12
  • 249
  • 242
  • Thanks. I specifically want to use a redis hash. I though of just storing unix seconds, from GMT and this will work. At the end i was hoping there is a translator function for structs -> redis hash (like `redis.Args{key}.AddFlat(data)` that does some kind of serialization. A struct might have many fields. Setting all the fields manually is inconvenient. Might put something forward to redigo. Another mechanism could be: `conn.Do("HSET", redis.Args{key}.AddFlat(data)...) \n conn.Do("HSET", key, "timestamp", data.Timestamp.Format(time.RFC3339))` – Oliver Becher Jul 05 '21 at 10:34
0

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
}

Akash C
  • 31
  • 5