diff --git a/commands.go b/commands.go index d791753..b3ee715 100644 --- a/commands.go +++ b/commands.go @@ -221,10 +221,11 @@ type Cmdable interface { ScriptingFunctionsCmdable StringCmdable PubSubCmdable + StreamCmdable GearsCmdable ProbabilisticCmdable TimeseriesCmdable - StreamCmdable + JSONCmdable } type StatefulCmdable interface { diff --git a/json.go b/json.go new file mode 100644 index 0000000..f942524 --- /dev/null +++ b/json.go @@ -0,0 +1,606 @@ +package redis + +import ( + "context" + "encoding/json" + "strings" + + "github.com/redis/go-redis/v9/internal/proto" + "github.com/redis/go-redis/v9/internal/util" +) + +// ------------------------------------------- + +type JSONCmdable interface { + JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd + JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd + JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd + JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd + JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd + JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd + JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd + JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimArgs) *IntSliceCmd + JSONClear(ctx context.Context, key, path string) *IntCmd + JSONDebugMemory(ctx context.Context, key, path string) *IntCmd + JSONDel(ctx context.Context, key, path string) *IntCmd + JSONForget(ctx context.Context, key, path string) *IntCmd + JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd + JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs, paths ...string) *JSONCmd + JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd + JSONMSetArgs(ctx context.Context, docs []JSONSetArgs) *StatusCmd + JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd + JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd + JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd + JSONObjKeys(ctx context.Context, key, path string) *SliceCmd + JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd + JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd + JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd + JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd + JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd + JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd + JSONType(ctx context.Context, key, path string) *JSONSliceCmd +} + +type JSONSetArgs struct { + Key string + Path string + Value interface{} +} + +type JSONArrIndexArgs struct { + Start int + Stop *int +} + +type JSONArrTrimArgs struct { + Start int + Stop *int +} + +type JSONCmd struct { + baseCmd + val string + expanded []interface{} +} + +var _ Cmder = (*JSONCmd)(nil) + +func newJSONCmd(ctx context.Context, args ...interface{}) *JSONCmd { + + return &JSONCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *JSONCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *JSONCmd) SetVal(val string) { + cmd.val = val +} + +func (cmd *JSONCmd) Val() string { + if len(cmd.val) == 0 && cmd.expanded != nil { + val, err := json.Marshal(cmd.expanded) + if err != nil { + cmd.SetErr(err) + return "" + } + return string(val) + + } else { + return cmd.val + } + +} + +func (cmd *JSONCmd) Result() (string, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd JSONCmd) Expanded() (interface{}, error) { + + if len(cmd.val) != 0 && cmd.expanded == nil { + err := json.Unmarshal([]byte(cmd.val), &cmd.expanded) + if err != nil { + return "", err + } + } + + return cmd.expanded, nil +} + +func (cmd *JSONCmd) readReply(rd *proto.Reader) error { + + // nil response from JSON.(M)GET (cmd.baseCmd.err will be "redis: nil") + if cmd.baseCmd.Err() == Nil { + cmd.val = "" + return Nil + } + + if readType, err := rd.PeekReplyType(); err != nil { + return err + } else if readType == proto.RespArray { + + size, err := rd.ReadArrayLen() + if err != nil { + return err + } + + var expanded = make([]interface{}, size) + + for i := 0; i < size; i++ { + if expanded[i], err = rd.ReadReply(); err != nil { + return err + } + } + cmd.expanded = expanded + + } else { + if str, err := rd.ReadString(); err != nil && err != Nil { + return err + } else if str == "" || err == Nil { + cmd.val = "" + } else { + cmd.val = str + } + } + + return nil +} + +// ------------------------------------------- + +type JSONSliceCmd struct { + baseCmd + val []interface{} +} + +func NewJSONSliceCmd(ctx context.Context, args ...interface{}) *JSONSliceCmd { + return &JSONSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *JSONSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *JSONSliceCmd) SetVal(val []interface{}) { + cmd.val = val +} + +func (cmd *JSONSliceCmd) Val() []interface{} { + return cmd.val +} + +func (cmd *JSONSliceCmd) Result() ([]interface{}, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *JSONSliceCmd) readReply(rd *proto.Reader) error { + + if cmd.baseCmd.Err() == Nil { + cmd.val = nil + return Nil + } + + if readType, err := rd.PeekReplyType(); err != nil { + return err + } else if readType == proto.RespArray { + response, err := rd.ReadReply() + if err != nil { + return nil + } else { + cmd.val = response.([]interface{}) + } + + } else { + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val = make([]interface{}, n) + for i := 0; i < len(cmd.val); i++ { + switch s, err := rd.ReadString(); { + case err == Nil: + cmd.val[i] = "" + case err != nil: + return err + default: + cmd.val[i] = s + } + } + } + return nil + +} + +/******************************************************************************* +* +* IntPointerSliceCmd +* used to represent a RedisJSON response where the result is either an integer or nil +* +*******************************************************************************/ + +type IntPointerSliceCmd struct { + baseCmd + val []*int64 +} + +// NewIntPointerSliceCmd initialises an IntPointerSliceCmd +func NewIntPointerSliceCmd(ctx context.Context, args ...interface{}) *IntPointerSliceCmd { + return &IntPointerSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *IntPointerSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *IntPointerSliceCmd) SetVal(val []*int64) { + cmd.val = val +} + +func (cmd *IntPointerSliceCmd) Val() []*int64 { + return cmd.val +} + +func (cmd *IntPointerSliceCmd) Result() ([]*int64, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *IntPointerSliceCmd) readReply(rd *proto.Reader) error { + + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + cmd.val = make([]*int64, n) + + for i := 0; i < len(cmd.val); i++ { + val, err := rd.ReadInt() + if err != nil && err != Nil { + return err + } else if err != Nil { + cmd.val[i] = &val + } + } + + return nil +} + +//------------------------------------------------------------------------------ + +// JSONArrAppend adds the provided JSON values to the end of the array at the given path. +// For more information, see https://redis.io/commands/json.arrappend +func (c cmdable) JSONArrAppend(ctx context.Context, key, path string, values ...interface{}) *IntSliceCmd { + args := []interface{}{"JSON.ARRAPPEND", key, path} + args = append(args, values...) + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrIndex searches for the first occurrence of the provided JSON value in the array at the given path. +// For more information, see https://redis.io/commands/json.arrindex +func (c cmdable) JSONArrIndex(ctx context.Context, key, path string, value ...interface{}) *IntSliceCmd { + args := []interface{}{"JSON.ARRINDEX", key, path} + args = append(args, value...) + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrIndexWithArgs searches for the first occurrence of a JSON value in an array while allowing the start and +// stop options to be provided. +// For more information, see https://redis.io/commands/json.arrindex +func (c cmdable) JSONArrIndexWithArgs(ctx context.Context, key, path string, options *JSONArrIndexArgs, value ...interface{}) *IntSliceCmd { + args := []interface{}{"JSON.ARRINDEX", key, path} + args = append(args, value...) + + if options != nil { + args = append(args, options.Start) + if options.Stop != nil { + args = append(args, *options.Stop) + } + } + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrInsert inserts the JSON values into the array at the specified path before the index (shifts to the right). +// For more information, see https://redis.io/commands/json.arrinsert +func (c cmdable) JSONArrInsert(ctx context.Context, key, path string, index int64, values ...interface{}) *IntSliceCmd { + args := []interface{}{"JSON.ARRINSERT", key, path, index} + args = append(args, values...) + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrLen reports the length of the JSON array at the specified path in the given key. +// For more information, see https://redis.io/commands/json.arrlen +func (c cmdable) JSONArrLen(ctx context.Context, key, path string) *IntSliceCmd { + args := []interface{}{"JSON.ARRLEN", key, path} + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrPop removes and returns an element from the specified index in the array. +// For more information, see https://redis.io/commands/json.arrpop +func (c cmdable) JSONArrPop(ctx context.Context, key, path string, index int) *StringSliceCmd { + args := []interface{}{"JSON.ARRPOP", key, path, index} + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrTrim trims an array to contain only the specified inclusive range of elements. +// For more information, see https://redis.io/commands/json.arrtrim +func (c cmdable) JSONArrTrim(ctx context.Context, key, path string) *IntSliceCmd { + args := []interface{}{"JSON.ARRTRIM", key, path} + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONArrTrimWithArgs trims an array to contain only the specified inclusive range of elements. +// For more information, see https://redis.io/commands/json.arrtrim +func (c cmdable) JSONArrTrimWithArgs(ctx context.Context, key, path string, options *JSONArrTrimArgs) *IntSliceCmd { + args := []interface{}{"JSON.ARRTRIM", key, path} + + if options != nil { + args = append(args, options.Start) + + if options.Stop != nil { + args = append(args, *options.Stop) + } + } + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONClear clears container values (arrays/objects) and sets numeric values to 0. +// For more information, see https://redis.io/commands/json.clear +func (c cmdable) JSONClear(ctx context.Context, key, path string) *IntCmd { + args := []interface{}{"JSON.CLEAR", key, path} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONDebugMemory reports a value's memory usage in bytes (unimplemented) +// For more information, see https://redis.io/commands/json.debug-memory +func (c cmdable) JSONDebugMemory(ctx context.Context, key, path string) *IntCmd { + panic("not implemented") +} + +// JSONDel deletes a value. +// For more information, see https://redis.io/commands/json.del +func (c cmdable) JSONDel(ctx context.Context, key, path string) *IntCmd { + args := []interface{}{"JSON.DEL", key, path} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONForget deletes a value. +// For more information, see https://redis.io/commands/json.forget +func (c cmdable) JSONForget(ctx context.Context, key, path string) *IntCmd { + args := []interface{}{"JSON.FORGET", key, path} + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONGet returns the value at path in JSON serialized form. JSON.GET returns an +// array of strings. This function parses out the wrapping array but leaves the +// internal strings unprocessed by default (see Val()) +// For more information - https://redis.io/commands/json.get/ +func (c cmdable) JSONGet(ctx context.Context, key string, paths ...string) *JSONCmd { + args := make([]interface{}, len(paths)+2) + args[0] = "JSON.GET" + args[1] = key + for n, path := range paths { + args[n+2] = path + } + cmd := newJSONCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +type JSONGetArgs struct { + Indent string + Newline string + Space string +} + +// JSONGetWithArgs - Retrieves the value of a key from a JSON document. +// This function also allows for specifying additional options such as: +// Indention, NewLine and Space +// For more information - https://redis.io/commands/json.get/ +func (c cmdable) JSONGetWithArgs(ctx context.Context, key string, options *JSONGetArgs, paths ...string) *JSONCmd { + args := []interface{}{"JSON.GET", key} + if options != nil { + if options.Indent != "" { + args = append(args, "INDENT", options.Indent) + } + if options.Newline != "" { + args = append(args, "NEWLINE", options.Newline) + } + if options.Space != "" { + args = append(args, "SPACE", options.Space) + } + for _, path := range paths { + args = append(args, path) + } + } + cmd := newJSONCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONMerge merges a given JSON value into matching paths. +// For more information, see https://redis.io/commands/json.merge +func (c cmdable) JSONMerge(ctx context.Context, key, path string, value string) *StatusCmd { + args := []interface{}{"JSON.MERGE", key, path, value} + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONMGet returns the values at the specified path from multiple key arguments. +// Note - the arguments are reversed when compared with `JSON.MGET` as we want +// to follow the pattern of having the last argument be variable. +// For more information, see https://redis.io/commands/json.mget +func (c cmdable) JSONMGet(ctx context.Context, path string, keys ...string) *JSONSliceCmd { + args := make([]interface{}, len(keys)+1) + args[0] = "JSON.MGET" + for n, key := range keys { + args[n+1] = key + } + args = append(args, path) + cmd := NewJSONSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONMSetArgs sets or updates one or more JSON values according to the specified key-path-value triplets. +// For more information, see https://redis.io/commands/json.mset +func (c cmdable) JSONMSetArgs(ctx context.Context, docs []JSONSetArgs) *StatusCmd { + args := []interface{}{"JSON.MSET"} + for _, doc := range docs { + args = append(args, doc.Key, doc.Path, doc.Value) + } + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) JSONMSet(ctx context.Context, params ...interface{}) *StatusCmd { + args := []interface{}{"JSON.MSET"} + args = append(args, params...) + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONNumIncrBy increments the number value stored at the specified path by the provided number. +// For more information, see https://redis.io/commands/json.numincreby +func (c cmdable) JSONNumIncrBy(ctx context.Context, key, path string, value float64) *JSONCmd { + args := []interface{}{"JSON.NUMINCRBY", key, path, value} + cmd := newJSONCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONObjKeys returns the keys in the object that's referenced by the specified path. +// For more information, see https://redis.io/commands/json.objkeys +func (c cmdable) JSONObjKeys(ctx context.Context, key, path string) *SliceCmd { + args := []interface{}{"JSON.OBJKEYS", key, path} + cmd := NewSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONObjLen reports the number of keys in the JSON object at the specified path in the given key. +// For more information, see https://redis.io/commands/json.objlen +func (c cmdable) JSONObjLen(ctx context.Context, key, path string) *IntPointerSliceCmd { + args := []interface{}{"JSON.OBJLEN", key, path} + cmd := NewIntPointerSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONSet sets the JSON value at the given path in the given key. The value must be something that +// can be marshaled to JSON (using encoding/JSON) unless the argument is a string or a []byte when we assume that +// it can be passed directly as JSON. +// For more information, see https://redis.io/commands/json.set +func (c cmdable) JSONSet(ctx context.Context, key, path string, value interface{}) *StatusCmd { + return c.JSONSetMode(ctx, key, path, value, "") +} + +// JSONSetMode sets the JSON value at the given path in the given key and allows the mode to be set +// (the mode value must be "XX" or "NX"). The value must be something that can be marshaled to JSON (using encoding/JSON) unless +// the argument is a string or []byte when we assume that it can be passed directly as JSON. +// For more information, see https://redis.io/commands/json.set +func (c cmdable) JSONSetMode(ctx context.Context, key, path string, value interface{}, mode string) *StatusCmd { + var bytes []byte + var err error + switch v := value.(type) { + case string: + bytes = []byte(v) + case []byte: + bytes = v + default: + bytes, err = json.Marshal(v) + } + args := []interface{}{"JSON.SET", key, path, util.BytesToString(bytes)} + if mode != "" { + switch strings.ToUpper(mode) { + case "XX", "NX": + args = append(args, strings.ToUpper(mode)) + + default: + panic("redis: JSON.SET mode must be NX or XX") + } + } + cmd := NewStatusCmd(ctx, args...) + if err != nil { + cmd.SetErr(err) + } else { + _ = c(ctx, cmd) + } + return cmd +} + +// JSONStrAppend appends the JSON-string values to the string at the specified path. +// For more information, see https://redis.io/commands/json.strappend +func (c cmdable) JSONStrAppend(ctx context.Context, key, path, value string) *IntPointerSliceCmd { + args := []interface{}{"JSON.STRAPPEND", key, path, value} + cmd := NewIntPointerSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONStrLen reports the length of the JSON String at the specified path in the given key. +// For more information, see https://redis.io/commands/json.strlen +func (c cmdable) JSONStrLen(ctx context.Context, key, path string) *IntPointerSliceCmd { + args := []interface{}{"JSON.STRLEN", key, path} + cmd := NewIntPointerSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONToggle toggles a Boolean value stored at the specified path. +// For more information, see https://redis.io/commands/json.toggle +func (c cmdable) JSONToggle(ctx context.Context, key, path string) *IntPointerSliceCmd { + args := []interface{}{"JSON.TOGGLE", key, path} + cmd := NewIntPointerSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// JSONType reports the type of JSON value at the specified path. +// For more information, see https://redis.io/commands/json.type +func (c cmdable) JSONType(ctx context.Context, key, path string) *JSONSliceCmd { + args := []interface{}{"JSON.TYPE", key, path} + cmd := NewJSONSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} diff --git a/json_test.go b/json_test.go new file mode 100644 index 0000000..d527133 --- /dev/null +++ b/json_test.go @@ -0,0 +1,669 @@ +package redis_test + +import ( + "context" + + . "github.com/bsm/ginkgo/v2" + . "github.com/bsm/gomega" + "github.com/redis/go-redis/v9" +) + +type JSONGetTestStruct struct { + Hello string `json:"hello"` +} + +var _ = Describe("JSON Commands", Label("json"), func() { + + ctx := context.TODO() + var client *redis.Client + + BeforeEach(func() { + client = redis.NewClient(&redis.Options{Addr: ":6379"}) + Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(client.Close()).NotTo(HaveOccurred()) + }) + + Describe("arrays", Label("arrays"), func() { + + It("should JSONArrAppend", Label("json.arrappend", "json"), func() { + cmd1 := client.JSONSet(ctx, "append2", "$", `{"a": [10], "b": {"a": [12, 13]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrAppend(ctx, "append2", "$..a", 10) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]int64{2, 3})) + }) + + It("should JSONArrIndex and JSONArrIndexWithArgs", Label("json.arrindex", "json"), func() { + cmd1, err := client.JSONSet(ctx, "index1", "$", `{"a": [10], "b": {"a": [12, 10]}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd1).To(Equal("OK")) + + cmd2, err := client.JSONArrIndex(ctx, "index1", "$.b.a", 10).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal([]int64{1})) + + cmd3, err := client.JSONSet(ctx, "index2", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd3).To(Equal("OK")) + + res, err := client.JSONArrIndex(ctx, "index2", "$", 1).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(1))) + + res, err = client.JSONArrIndex(ctx, "index2", "$", 1, 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(-1))) + + res, err = client.JSONArrIndex(ctx, "index2", "$", 4).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(4))) + + res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{}, 4).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(4))) + + stop := 5000 + res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{Stop: &stop}, 4).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(4))) + + stop = -1 + res, err = client.JSONArrIndexWithArgs(ctx, "index2", "$", &redis.JSONArrIndexArgs{Stop: &stop}, 4).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res[0]).To(Equal(int64(-1))) + + }) + + It("should JSONArrIndex and JSONArrIndexWithArgs with $", Label("json.arrindex", "json"), func() { + doc := `{ + "store": { + "book": [ + { + "category": "reference", + "author": "Nigel Rees", + "title": "Sayings of the Century", + "price": 8.95, + "size": [10, 20, 30, 40] + }, + { + "category": "fiction", + "author": "Evelyn Waugh", + "title": "Sword of Honour", + "price": 12.99, + "size": [50, 60, 70, 80] + }, + { + "category": "fiction", + "author": "Herman Melville", + "title": "Moby Dick", + "isbn": "0-553-21311-3", + "price": 8.99, + "size": [5, 10, 20, 30] + }, + { + "category": "fiction", + "author": "J. R. R. Tolkien", + "title": "The Lord of the Rings", + "isbn": "0-395-19395-8", + "price": 22.99, + "size": [5, 6, 7, 8] + } + ], + "bicycle": {"color": "red", "price": 19.95} + } + }` + res, err := client.JSONSet(ctx, "doc1", "$", doc).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + resGet, err := client.JSONGet(ctx, "doc1", "$.store.book[?(@.price<10)].size").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal("[[10,20,30,40],[5,10,20,30]]")) + + resArr, err := client.JSONArrIndex(ctx, "doc1", "$.store.book[?(@.price<10)].size", 20).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resArr).To(Equal([]int64{1, 2})) + }) + + It("should JSONArrInsert", Label("json.arrinsert", "json"), func() { + cmd1 := client.JSONSet(ctx, "insert2", "$", `[100, 200, 300, 200]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrInsert(ctx, "insert2", "$", -1, 1, 2) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]int64{6})) + + cmd3 := client.JSONGet(ctx, "insert2") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + // RESP2 vs RESP3 + Expect(cmd3.Val()).To(Or( + Equal(`[100,200,300,1,2,200]`), + Equal(`[[100,200,300,1,2,200]]`))) + }) + + It("should JSONArrLen", Label("json.arrlen", "json"), func() { + cmd1 := client.JSONSet(ctx, "length2", "$", `{"a": [10], "b": {"a": [12, 10, 20, 12, 90, 10]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrLen(ctx, "length2", "$..a") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]int64{1, 6})) + }) + + It("should JSONArrPop", Label("json.arrpop"), func() { + cmd1 := client.JSONSet(ctx, "pop4", "$", `[100, 200, 300, 200]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrPop(ctx, "pop4", "$", 2) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]string{"300"})) + + cmd3 := client.JSONGet(ctx, "pop4", "$") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + Expect(cmd3.Val()).To(Equal("[[100,200,200]]")) + }) + + It("should JSONArrTrim", Label("json.arrtrim", "json"), func() { + cmd1, err := client.JSONSet(ctx, "trim1", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd1).To(Equal("OK")) + + stop := 3 + cmd2, err := client.JSONArrTrimWithArgs(ctx, "trim1", "$", &redis.JSONArrTrimArgs{Start: 1, Stop: &stop}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal([]int64{3})) + + res, err := client.JSONGet(ctx, "trim1", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(`[[1,2,3]]`)) + + cmd3, err := client.JSONSet(ctx, "trim2", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd3).To(Equal("OK")) + + stop = 3 + cmd4, err := client.JSONArrTrimWithArgs(ctx, "trim2", "$", &redis.JSONArrTrimArgs{Start: -1, Stop: &stop}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd4).To(Equal([]int64{0})) + + cmd5, err := client.JSONSet(ctx, "trim3", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd5).To(Equal("OK")) + + stop = 99 + cmd6, err := client.JSONArrTrimWithArgs(ctx, "trim3", "$", &redis.JSONArrTrimArgs{Start: 3, Stop: &stop}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd6).To(Equal([]int64{2})) + + cmd7, err := client.JSONSet(ctx, "trim4", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd7).To(Equal("OK")) + + stop = 1 + cmd8, err := client.JSONArrTrimWithArgs(ctx, "trim4", "$", &redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd8).To(Equal([]int64{0})) + + cmd9, err := client.JSONSet(ctx, "trim5", "$", `[0,1,2,3,4]`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd9).To(Equal("OK")) + + stop = 11 + cmd10, err := client.JSONArrTrimWithArgs(ctx, "trim5", "$", &redis.JSONArrTrimArgs{Start: 9, Stop: &stop}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd10).To(Equal([]int64{0})) + }) + + It("should JSONArrPop", Label("json.arrpop", "json"), func() { + cmd1 := client.JSONSet(ctx, "pop4", "$", `[100, 200, 300, 200]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONArrPop(ctx, "pop4", "$", 2) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal([]string{"300"})) + + cmd3 := client.JSONGet(ctx, "pop4", "$") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + Expect(cmd3.Val()).To(Equal("[[100,200,200]]")) + }) + + }) + + Describe("get/set", Label("getset"), func() { + It("should JSONSet", Label("json.set", "json"), func() { + cmd := client.JSONSet(ctx, "set1", "$", `{"a": 1, "b": 2, "hello": "world"}`) + Expect(cmd.Err()).NotTo(HaveOccurred()) + Expect(cmd.Val()).To(Equal("OK")) + }) + + It("should JSONGet", Label("json.get", "json"), func() { + res, err := client.JSONSet(ctx, "get3", "$", `{"a": 1, "b": 2}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "-"}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(`[-{--"a":1,--"b":2-}]`)) + + res, err = client.JSONGetWithArgs(ctx, "get3", &redis.JSONGetArgs{Indent: "-", Newline: `~`, Space: `!`}).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(`[~-{~--"a":!1,~--"b":!2~-}~]`)) + + }) + + It("should JSONMerge", Label("json.merge", "json"), func() { + res, err := client.JSONSet(ctx, "merge1", "$", `{"a": 1, "b": 2}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + res, err = client.JSONMerge(ctx, "merge1", "$", `{"b": 3, "c": 4}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + res, err = client.JSONGet(ctx, "merge1", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(`[{"a":1,"b":3,"c":4}]`)) + }) + + It("should JSONMSet", Label("json.mset", "json"), func() { + doc1 := redis.JSONSetArgs{Key: "mset1", Path: "$", Value: `{"a": 1}`} + doc2 := redis.JSONSetArgs{Key: "mset2", Path: "$", Value: 2} + docs := []redis.JSONSetArgs{doc1, doc2} + + mSetResult, err := client.JSONMSetArgs(ctx, docs).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(mSetResult).To(Equal("OK")) + + res, err := client.JSONMGet(ctx, "$", "mset1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal([]interface{}{`[{"a":1}]`})) + + res, err = client.JSONMGet(ctx, "$", "mset1", "mset2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal([]interface{}{`[{"a":1}]`, "[2]"})) + + mSetResult, err = client.JSONMSet(ctx, "mset1", "$.a", 2, "mset3", "$", `[1]`).Result() + Expect(err).NotTo(HaveOccurred()) + }) + + It("should JSONMGet", Label("json.mget", "json"), func() { + cmd1 := client.JSONSet(ctx, "mget2a", "$", `{"a": ["aa", "ab", "ac", "ad"], "b": {"a": ["ba", "bb", "bc", "bd"]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + cmd2 := client.JSONSet(ctx, "mget2b", "$", `{"a": [100, 200, 300, 200], "b": {"a": [100, 200, 300, 200]}}`) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal("OK")) + + cmd3 := client.JSONMGet(ctx, "$..a", "mget2a", "mget2b") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + Expect(cmd3.Val()).To(HaveLen(2)) + Expect(cmd3.Val()[0]).To(Equal(`[["aa","ab","ac","ad"],["ba","bb","bc","bd"]]`)) + Expect(cmd3.Val()[1]).To(Equal(`[[100,200,300,200],[100,200,300,200]]`)) + }) + + It("should JSONMget with $", Label("json.mget", "json"), func() { + res, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "b": 2, "nested": {"a": 3}, "c": "", "nested2": {"a": ""}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + res, err = client.JSONSet(ctx, "doc2", "$", `{"a": 4, "b": 5, "nested": {"a": 6}, "c": "", "nested2": {"a": [""]}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + iRes, err := client.JSONMGet(ctx, "$..a", "doc1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(iRes).To(Equal([]interface{}{`[1,3,""]`})) + + iRes, err = client.JSONMGet(ctx, "$..a", "doc1", "doc2").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(iRes).To(Equal([]interface{}{`[1,3,""]`, `[4,6,[""]]`})) + + iRes, err = client.JSONMGet(ctx, "$..a", "non_existing_doc", "non_existing_doc1").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(iRes).To(Equal([]interface{}{nil, nil})) + + }) + + }) + + Describe("Misc", Label("misc"), func() { + + It("should JSONClear", Label("json.clear", "json"), func() { + cmd1 := client.JSONSet(ctx, "clear1", "$", `[1]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONClear(ctx, "clear1", "$") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal(int64(1))) + + cmd3 := client.JSONGet(ctx, "clear1", "$") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + Expect(cmd3.Val()).To(Equal(`[[]]`)) + }) + + It("should JSONClear with $", Label("json.clear", "json"), func() { + doc := `{ + "nested1": {"a": {"foo": 10, "bar": 20}}, + "a": ["foo"], + "nested2": {"a": "claro"}, + "nested3": {"a": {"baz": 50}} + }` + res, err := client.JSONSet(ctx, "doc1", "$", doc).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + iRes, err := client.JSONClear(ctx, "doc1", "$..a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(iRes).To(Equal(int64(3))) + + resGet, err := client.JSONGet(ctx, "doc1", `$`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(`[{"nested1":{"a":{}},"a":[],"nested2":{"a":"claro"},"nested3":{"a":{}}}]`)) + + res, err = client.JSONSet(ctx, "doc1", "$", doc).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + iRes, err = client.JSONClear(ctx, "doc1", "$.nested1.a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(iRes).To(Equal(int64(1))) + + resGet, err = client.JSONGet(ctx, "doc1", `$`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(`[{"nested1":{"a":{}},"a":["foo"],"nested2":{"a":"claro"},"nested3":{"a":{"baz":50}}}]`)) + }) + + It("should JSONDel", Label("json.del", "json"), func() { + cmd1 := client.JSONSet(ctx, "del1", "$", `[1]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONDel(ctx, "del1", "$") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal(int64(1))) + + cmd3 := client.JSONGet(ctx, "del1", "$") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + Expect(cmd3.Val()).To(HaveLen(0)) + }) + + It("should JSONDel with $", Label("json.del", "json"), func() { + res, err := client.JSONSet(ctx, "del1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + iRes, err := client.JSONDel(ctx, "del1", "$..a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(iRes).To(Equal(int64(2))) + + resGet, err := client.JSONGet(ctx, "del1", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(`[{"nested":{"b":3}}]`)) + + res, err = client.JSONSet(ctx, "del2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + iRes, err = client.JSONDel(ctx, "del2", "$..a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(iRes).To(Equal(int64(1))) + + resGet, err = client.JSONGet(ctx, "del2", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(`[{"nested":{"b":[true,"a","b"]},"b":["a","b"]}]`)) + + doc := `[ + { + "ciao": ["non ancora"], + "nested": [ + {"ciao": [1, "a"]}, + {"ciao": [2, "a"]}, + {"ciaoc": [3, "non", "ciao"]}, + {"ciao": [4, "a"]}, + {"e": [5, "non", "ciao"]} + ] + } + ]` + res, err = client.JSONSet(ctx, "del3", "$", doc).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + iRes, err = client.JSONDel(ctx, "del3", `$.[0]["nested"]..ciao`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(iRes).To(Equal(int64(3))) + + resVal := `[[{"ciao":["non ancora"],"nested":[{},{},{"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]]` + resGet, err = client.JSONGet(ctx, "del3", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(resVal)) + }) + + It("should JSONForget", Label("json.forget", "json"), func() { + cmd1 := client.JSONSet(ctx, "forget3", "$", `{"a": [1,2,3], "b": {"a": [1,2,3], "b": "annie"}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONForget(ctx, "forget3", "$..a") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal(int64(2))) + + cmd3 := client.JSONGet(ctx, "forget3", "$") + Expect(cmd3.Err()).NotTo(HaveOccurred()) + Expect(cmd3.Val()).To(Equal(`[{"b":{"b":"annie"}}]`)) + + }) + + It("should JSONForget with $", Label("json.forget", "json"), func() { + res, err := client.JSONSet(ctx, "doc1", "$", `{"a": 1, "nested": {"a": 2, "b": 3}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + iRes, err := client.JSONForget(ctx, "doc1", "$..a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(iRes).To(Equal(int64(2))) + + resGet, err := client.JSONGet(ctx, "doc1", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(`[{"nested":{"b":3}}]`)) + + res, err = client.JSONSet(ctx, "doc2", "$", `{"a": {"a": 2, "b": 3}, "b": ["a", "b"], "nested": {"b": [true, "a", "b"]}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + iRes, err = client.JSONForget(ctx, "doc2", "$..a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(iRes).To(Equal(int64(1))) + + resGet, err = client.JSONGet(ctx, "doc2", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(`[{"nested":{"b":[true,"a","b"]},"b":["a","b"]}]`)) + + doc := `[ + { + "ciao": ["non ancora"], + "nested": [ + {"ciao": [1, "a"]}, + {"ciao": [2, "a"]}, + {"ciaoc": [3, "non", "ciao"]}, + {"ciao": [4, "a"]}, + {"e": [5, "non", "ciao"]} + ] + } + ]` + res, err = client.JSONSet(ctx, "doc3", "$", doc).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + iRes, err = client.JSONForget(ctx, "doc3", `$.[0]["nested"]..ciao`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(iRes).To(Equal(int64(3))) + + resVal := `[[{"ciao":["non ancora"],"nested":[{},{},{"ciaoc":[3,"non","ciao"]},{},{"e":[5,"non","ciao"]}]}]]` + resGet, err = client.JSONGet(ctx, "doc3", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(resGet).To(Equal(resVal)) + }) + + It("should JSONNumIncrBy", Label("json.numincrby", "json"), func() { + cmd1 := client.JSONSet(ctx, "incr3", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONNumIncrBy(ctx, "incr3", "$..a[1]", float64(1)) + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(Equal(`[3,0]`)) + }) + + It("should JSONNumIncrBy with $", Label("json.numincrby", "json"), func() { + res, err := client.JSONSet(ctx, "doc1", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + res, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 2).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(`[7]`)) + + res, err = client.JSONNumIncrBy(ctx, "doc1", "$.b[1].a", 3.5).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(`[10.5]`)) + + res, err = client.JSONSet(ctx, "doc2", "$", `{"a": "b", "b": [{"a": 2}, {"a": 5.0}, {"a": "c"}]}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + res, err = client.JSONNumIncrBy(ctx, "doc2", "$.b[0].a", 3).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal(`[5]`)) + }) + + It("should JSONObjKeys", Label("json.objkeys", "json"), func() { + cmd1 := client.JSONSet(ctx, "objkeys1", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONObjKeys(ctx, "objkeys1", "$..*") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(HaveLen(7)) + Expect(cmd2.Val()).To(Equal([]interface{}{nil, []interface{}{"a"}, nil, nil, nil, nil, nil})) + }) + + It("should JSONObjKeys with $", Label("json.objkeys", "json"), func() { + doc := `{ + "nested1": {"a": {"foo": 10, "bar": 20}}, + "a": ["foo"], + "nested2": {"a": {"baz": 50}} + }` + cmd1, err := client.JSONSet(ctx, "objkeys1", "$", doc).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd1).To(Equal("OK")) + + cmd2, err := client.JSONObjKeys(ctx, "objkeys1", "$.nested1.a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal([]interface{}{[]interface{}{"foo", "bar"}})) + + cmd2, err = client.JSONObjKeys(ctx, "objkeys1", ".*.a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal([]interface{}{"foo", "bar"})) + + cmd2, err = client.JSONObjKeys(ctx, "objkeys1", ".nested2.a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd2).To(Equal([]interface{}{"baz"})) + + _, err = client.JSONObjKeys(ctx, "non_existing_doc", "..a").Result() + Expect(err).To(HaveOccurred()) + }) + + It("should JSONObjLen", Label("json.objlen", "json"), func() { + cmd1 := client.JSONSet(ctx, "objlen2", "$", `{"a": [1, 2], "b": {"a": [0, -1]}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONObjLen(ctx, "objlen2", "$..*") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(HaveLen(7)) + Expect(cmd2.Val()[0]).To(BeNil()) + Expect(*cmd2.Val()[1]).To(Equal(int64(1))) + }) + + It("should JSONStrLen", Label("json.strlen", "json"), func() { + cmd1 := client.JSONSet(ctx, "strlen2", "$", `{"a": "alice", "b": "bob", "c": {"a": "alice", "b": "bob"}}`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONStrLen(ctx, "strlen2", "$..*") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(HaveLen(5)) + var tmp int64 = 20 + Expect(cmd2.Val()[0]).To(BeAssignableToTypeOf(&tmp)) + Expect(*cmd2.Val()[0]).To(Equal(int64(5))) + Expect(*cmd2.Val()[1]).To(Equal(int64(3))) + Expect(cmd2.Val()[2]).To(BeNil()) + Expect(*cmd2.Val()[3]).To(Equal(int64(5))) + Expect(*cmd2.Val()[4]).To(Equal(int64(3))) + }) + + It("should JSONStrAppend", Label("json.strappend", "json"), func() { + cmd1, err := client.JSONSet(ctx, "strapp1", "$", `"foo"`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd1).To(Equal("OK")) + cmd2, err := client.JSONStrAppend(ctx, "strapp1", "$", `"bar"`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(*cmd2[0]).To(Equal(int64(6))) + cmd3, err := client.JSONGet(ctx, "strapp1", "$").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(cmd3).To(Equal(`["foobar"]`)) + + }) + + It("should JSONStrAppend and JSONStrLen with $", Label("json.strappend", "json.strlen", "json"), func() { + res, err := client.JSONSet(ctx, "doc1", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + intArrayResult, err := client.JSONStrAppend(ctx, "doc1", "$.nested1.a", `"baz"`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(*intArrayResult[0]).To(Equal(int64(8))) + + res, err = client.JSONSet(ctx, "doc2", "$", `{"a": "foo", "nested1": {"a": "hello"}, "nested2": {"a": 31}}`).Result() + Expect(err).NotTo(HaveOccurred()) + Expect(res).To(Equal("OK")) + + intResult, err := client.JSONStrLen(ctx, "doc2", "$.nested1.a").Result() + Expect(err).NotTo(HaveOccurred()) + Expect(*intResult[0]).To(Equal(int64(5))) + }) + + It("should JSONToggle", Label("json.toggle", "json"), func() { + cmd1 := client.JSONSet(ctx, "toggle1", "$", `[true]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONToggle(ctx, "toggle1", "$[0]") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(HaveLen(1)) + Expect(*cmd2.Val()[0]).To(Equal(int64(0))) + }) + + It("should JSONType", Label("json.type", "json"), func() { + cmd1 := client.JSONSet(ctx, "type1", "$", `[true]`) + Expect(cmd1.Err()).NotTo(HaveOccurred()) + Expect(cmd1.Val()).To(Equal("OK")) + + cmd2 := client.JSONType(ctx, "type1", "$[0]") + Expect(cmd2.Err()).NotTo(HaveOccurred()) + Expect(cmd2.Val()).To(HaveLen(1)) + // RESP2 v RESP3 + Expect(cmd2.Val()[0]).To(Or(Equal([]interface{}{"boolean"}), Equal("boolean"))) + }) + }) +})