Easy enough right ? Let’s see something more involved …

Embedded Objects in Go Structs

Now let’s take a more complex structure,

type Student struct {

Info *StudentDetails `json:”info,omitempty”`

Rank int `json:”rank,omitempty”`

} type StudentDetails struct {

FirstName string

LastName string

Major string

} studentJD := Student{

Info: &StudentDetails{

FirstName: “John”,

LastName: “Doe”,

Major: “CSE”,

},

Rank: 1,

}

What we have in our hands now is an embedded struct, with StudentDetails , as a member of the Student object.

Let’s try using HMSET again,

// Invoke the command using the Do command

_, err = conn.Do(“HMSET”, redis.Args{“JohnDoe”}.AddFlat(studentJD)…)

if err != nil {

return

}

If we look at redis at this point, we will see the info object to be stored as –

127.0.0.1:6379> HGETALL JohnDoe

Info

&{John Doe CSE}

Rank

1

Now this is the problem bit. When we try to retrieve the information back into the object, ScanStruct fails with the error,

redigo.ScanStruct: cannot assign field Info: cannot convert from Redis bulk string to *main.StudentDetails

EPIC FAIL !

This happened because in redis everything is stored as a string [bulk string for larger objects].

What Now ?

A quick search would take you to a couple of solutions. One of those solutions suggest using a Marshaler ( JSON marshal) and others suggest MessagePack .

I am going to present the JSON based solution below.

b, err := json.Marshal(&studentJD)

if err != nil {

return

} _, err = conn.Do(“SET”, “JohnDoe”, string(b))

if err != nil {

return

}

To retrieve it just read the JSON string back using the GET command

objStr, err = redis.String(conn.Do(“GET”, “JohnDoe”))

if err != nil {

return

} b := []byte(objStr) student := &Student{} err = json.Unmarshal(b, student)

if err != nil {

return

}

Well this works great, if all we wish to do is cache the object in its entirety.

What if we wanted to just add, modify or read one of the fields, for example, if John Doe changed his major to EE from CSE ??

The only way we can do this is by reading the JSON string, Un-marshaling it into the object, modifying the object and re-writing it into redis. That seems like a lot of work !

If you are wondering, doing this with the Hash is trivial by using the HGET / HSET commands. If only, that worked — bummer!

Just cause, no web-post can exist without a meme …

ReJSON

The excellent team at RedisLabs sweat it out and brought us a solution, that lets us treat our objects as traditional JSON objects.

Lets jump right into it. I pick this example straight from the rejson documentation,

127.0.0.1:6379> JSON.SET amoreinterestingexample . ‘[ true, { “answer”: 42 }, null ]’

OK

127.0.0.1:6379> JSON.GET amoreinterestingexample

“[true,{\”answer\”:42},null]”

127.0.0.1:6379> JSON.GET amoreinterestingexample [1].answer

“42”

127.0.0.1:6379> JSON.DEL amoreinterestingexample [-1]

1

127.0.0.1:6379> JSON.GET amoreinterestingexample

“[true,{\”answer\”:42}]”

To do this programmatically, we could definitely use Redigo in its raw form. [it’s meant to support any commands supported by Redis, using the conn.Do(…) method].

However, I took some time to turn all the ReJSON commands, into a Go convenience package, called go-rejson.

Going back to our Student object, we could programmatically add it into Redis using the following step.

import "github.com/nitishm/go-rejson" _, err = rejson.JSONSet(conn, “JohnDoeJSON, “.”, studentJD, false, false)

if err != nil {

return

}

A quick inspection in redis-cli gives us,

127.0.0.1:6379> JSON.GET JohnDoeJSON

{“info”:{“FirstName”:”John”,”LastName”:”Doe”,”Major”:”CSE”},”rank”:1}

If I wish to just read the info field from the redis entry I would perform a JSON.SET as follows,

127.0.0.1:6379> JSON.GET JohnDoeJSON .info

{“FirstName”:”John”,”LastName”:”Doe”,”Major”:”CSE”}

Similarly with the rank field, I could reference the .rank ,

127.0.0.1:6379> JSON.GET JohnDoeJSON .rank

1

To programmatically retrieve the student object, we would use the JSON.GET command via the JSONGet() method,

v, err := rejson.JSONGet(conn, “JohnDoeJSON, “”)

if err != nil {

return

} outStudent := &Student{}

err = json.Unmarshal(outJSON.([]byte), outStudent)

if err != nil {

return

}

To set the rank field we could use the JSON.SET command on the .rank field using the JSONSet() method,

_, err = rejson.JSONSet(conn, “JohnDoeJSON, “.info.Major”, “EE”, false, false)

if err != nil {

return

}

Inspecting the entry in redis-cli , we get,