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