123456789_123456789_123456789_123456789_123456789_

Module: Redis::Commands::Json

Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Included In:
Defined in: lib/redis/commands/modules/json.rb

Overview

::Redis::Commands for the RedisJSON module (built into ::Redis core since 8.0, or available earlier through the RedisJSON module in ::Redis Stack).

Values are serialized to JSON text on the client before they are sent, and replies that carry JSON text are parsed back into Ruby objects. The serialized form of these replies is identical under RESP2 and RESP3 (RESP3 has no native JSON type), so the handling here is protocol-independent. Note that some other RedisJSON commands — e.g. JSON.NUMINCRBY — DO return differently shaped replies under RESP3 and will need protocol-aware reshaping once RESP3 is supported at this layer.

Constant Summary

  • JSON_SET_FPHA_TYPES = private

    Floating-point precisions accepted by JSON.SET's FPHA argument (Redis 8.8+).

    # File 'lib/redis/commands/modules/json.rb', line 18
    %w[BF16 FP16 FP32 FP64].freeze
  • NumincrbyNormalize =

    Normalize a JSON.NUMINCRBY reply to a protocol-independent value: an array of numbers for a JSONPath, a single number for a legacy path. Under RESP2 the result arrives as a JSON-encoded string ("[3,4]" / "7"); under RESP3 it arrives as native numbers (an array, even for a legacy path). Both shapes map to the same value.

    A legacy path can also match multiple values (e.g. "..a"). RESP2 collapses those to the last changed scalar ("7"), while RESP3 returns the full match array with nil for every non-numeric match ([nil, 4, 7, nil]). To stay RESP2-compatible we take the last non-nil element of that array, not its first.

    # File 'lib/redis/commands/modules/json.rb', line 30
    lambda do |reply, jsonpath|
      return nil if reply.nil?
    
      reply = ::JSON.parse(reply) if reply.is_a?(String) # RESP2 string form
      if jsonpath
        Array(reply)
      elsif reply.is_a?(Array)
        reply.compact.last # RESP3 wraps even a legacy reply in an array; RESP2 keeps the last changed scalar
      else
        reply
      end
    end
  • TypeNormalize =

    Normalize a JSON.TYPE reply to a protocol-independent value: an array of type strings for a JSONPath, a single type string for a legacy path. RESP2 returns ["integer"] / "integer"; RESP3 nests one level further as [["integer"]] / ["integer"]. Both map to the same value.

    # File 'lib/redis/commands/modules/json.rb', line 46
    lambda do |reply, jsonpath|
      return nil if reply.nil?
    
      if jsonpath
        reply.flatten # RESP2 ["integer"] stays flat; RESP3 [["integer"]] collapses
      elsif reply.is_a?(Array)
        reply.first # RESP3 legacy ["integer"] -> "integer"
      else
        reply # RESP2 legacy "integer"
      end
    end

Instance Method Summary

Instance Method Details

#json_arrappend(key, path, *values, raw: false) ⇒ Array<Integer>, Integer

Append one or more values to the array at path in the document stored under key.

By default each value is a Ruby object serialized with JSON.generate; pass raw: true to send already-encoded JSON strings through untouched.

Examples:

redis.json_arrappend("doc", "$.colors", "blue")
  # => [3]

Parameters:

  • key (String)
  • path (String)

    a JSONPath to the target array

  • values (Array<Object>)

    one or more JSON values to append

  • raw (Boolean)

    treat each value as an already-encoded JSON string

Returns:

  • (Array<Integer>, Integer)

    the new array length(s); an Array for a JSONPath, a single Integer for a legacy path (nil for a match that is not an array)

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 282

def json_arrappend(key, path, *values, raw: false)
  values = values.map { |value| ::JSON.generate(value) } unless raw
  send_command([:"JSON.ARRAPPEND", key, path, *values])
end

#json_arrindex(key, path, value, start: nil, stop: nil, raw: false) ⇒ Array<Integer>, Integer

Return the index of the first occurrence of a scalar value in the array at path. The optional start (inclusive) and stop (exclusive) bound the search.

Examples:

redis.json_arrindex("doc", "$.colors", "silver")
  # => [1]

Parameters:

  • key (String)
  • path (String)

    a JSONPath to the target array

  • value (Object)

    the scalar JSON value to search for

  • start (Integer)

    optional inclusive start index

  • stop (Integer)

    optional exclusive stop index

  • raw (Boolean)

    treat value as an already-encoded JSON string

Returns:

  • (Array<Integer>, Integer)

    the index/indices of the first match (-1 if not found)

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 301

def json_arrindex(key, path, value, start: nil, stop: nil, raw: false)
  value = ::JSON.generate(value) unless raw
  args = [:"JSON.ARRINDEX", key, path, value]
  unless start.nil? && stop.nil?
    args << Integer(start || 0)
    args << Integer(stop) unless stop.nil?
  end
  send_command(args)
end

#json_arrinsert(key, path, index, *values, raw: false) ⇒ Array<Integer>, Integer

Insert one or more values into the array at path before index (existing elements at and after index shift right; 0 prepends, negative counts from the end).

By default each value is a Ruby object serialized with JSON.generate; pass raw: true to send already-encoded JSON strings through untouched.

Examples:

redis.json_arrinsert("doc", "$.colors", 1, "gold")
  # => [4]

Parameters:

  • key (String)
  • path (String)

    a JSONPath to the target array

  • index (Integer)

    the position to insert before

  • values (Array<Object>)

    one or more JSON values to insert

  • raw (Boolean)

    treat each value as an already-encoded JSON string

Returns:

  • (Array<Integer>, Integer)

    the new array length(s)

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 327

def json_arrinsert(key, path, index, *values, raw: false)
  values = values.map { |value| ::JSON.generate(value) } unless raw
  send_command([:"JSON.ARRINSERT", key, path, Integer(index), *values])
end

#json_arrlen(key, path = nil) ⇒ Array<Integer>, ...

Return the length of the array at path. When path is omitted it defaults to the root.

Examples:

redis.json_arrlen("doc", "$.colors")
  # => [2]

Parameters:

  • key (String)
  • path (String) (defaults to: nil)

    an optional JSONPath (defaults to the root "$")

Returns:

  • (Array<Integer>, Integer, nil)

    the array length(s); nil for a match that is not an array, or when the key/path does not exist

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 342

def json_arrlen(key, path = nil)
  args = [:"JSON.ARRLEN", key]
  args << path if path
  send_command(args)
end

#json_arrpop(key, path = nil, index = nil, raw: false) ⇒ Array, ...

Remove and return an element from the array at path. When path is omitted it defaults to the root; when index is omitted it defaults to -1 (the last element).

The popped element is returned as parsed JSON (a Ruby object), or as the unparsed JSON string when raw: true.

Examples:

redis.json_arrpop("doc", "$.colors", 0)
  # => ["black"]

Parameters:

  • key (String)
  • path (String) (defaults to: nil)

    an optional JSONPath to the target array (defaults to the root "$")

  • index (Integer) (defaults to: nil)

    an optional position to pop from (defaults to -1, the last element)

  • raw (Boolean)

    return the unparsed JSON string(s) instead of parsed Ruby objects

Returns:

  • (Array, Object, nil)

    the popped value(s); an Array for a JSONPath, a single value for a legacy path, nil for an empty array or a non-array match

Raises:

  • (ArgumentError)

    if index is given without a path

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 365

def json_arrpop(key, path = nil, index = nil, raw: false)
  raise ArgumentError, "index requires a path" if !index.nil? && path.nil?

  args = [:"JSON.ARRPOP", key]
  if path
    args << path
    args << Integer(index) unless index.nil?
  end

  send_command(args) do |reply|
    if reply.nil? || raw
      reply
    elsif reply.is_a?(Array)
      reply.map { |value| value.nil? ? nil : ::JSON.parse(value) }
    else
      ::JSON.parse(reply)
    end
  end
end

#json_arrtrim(key, path, start, stop) ⇒ Array<Integer>, Integer

Trim the array at path so it keeps only the inclusive range of elements from start to stop (negative indices count from the end).

Examples:

redis.json_arrtrim("doc", "$.colors", 0, 1)
  # => [2]

Parameters:

  • key (String)
  • path (String)

    a JSONPath to the target array

  • start (Integer)

    inclusive index of the first element to keep

  • stop (Integer)

    inclusive index of the last element to keep

Returns:

  • (Array<Integer>, Integer)

    the new array length(s)

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 397

def json_arrtrim(key, path, start, stop)
  send_command([:"JSON.ARRTRIM", key, path, Integer(start), Integer(stop)])
end

#json_clear(key, path = nil) ⇒ Integer

Clear the container and numeric value(s) at path: arrays and objects are emptied and numbers are set to 0. Strings, booleans and null are left unchanged. When path is omitted it defaults to the root.

Examples:

redis.json_clear("doc", "$.arr")
  # => 1

Parameters:

  • key (String)
  • path (String) (defaults to: nil)

    an optional JSONPath (defaults to the root "$")

Returns:

  • (Integer)

    the number of values cleared

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 238

def json_clear(key, path = nil)
  args = [:"JSON.CLEAR", key]
  args << path if path
  send_command(args)
end

#json_debug_memory(key, path = nil) ⇒ Array<Integer>, Integer

Report the size in bytes of the JSON value(s) at path (the JSON.DEBUG MEMORY subcommand). When path is omitted it defaults to the root.

Examples:

redis.json_debug_memory("doc")
  # => 264

Parameters:

  • key (String)
  • path (String) (defaults to: nil)

    an optional JSONPath (defaults to the root)

Returns:

  • (Array<Integer>, Integer)

    the size(s) in bytes: an Array for a JSONPath, a single integer for a legacy path or when no path is given (0 for a missing key)

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 535

def json_debug_memory(key, path = nil)
  args = [:"JSON.DEBUG", "MEMORY", key]
  args << path if path
  send_command(args)
end

#json_del(key, path = nil) ⇒ Integer

Delete the value(s) at path in the document stored under key. When path is omitted it defaults to the root, so deleting the root removes the whole key.

Examples:

redis.json_del("doc", "$.a")
  # => 1

Parameters:

  • key (String)
  • path (String) (defaults to: nil)

    an optional JSONPath (defaults to the root "$")

Returns:

  • (Integer)

    the number of values deleted

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 206

def json_del(key, path = nil)
  args = [:"JSON.DEL", key]
  args << path if path
  send_command(args)
end

#json_forget(key, path = nil) ⇒ Integer

Delete the value(s) at path in the document stored under key. Alias of #json_del.

Examples:

redis.json_forget("doc", "$.a")
  # => 1

Parameters:

  • key (String)
  • path (String) (defaults to: nil)

    an optional JSONPath (defaults to the root "$")

Returns:

  • (Integer)

    the number of values deleted

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 221

def json_forget(key, path = nil)
  args = [:"JSON.FORGET", key]
  args << path if path
  send_command(args)
end

#json_get(key, *paths, raw: false) ⇒ Object, ...

Get the JSON value(s) at one or more paths in the document stored under key.

By default the reply is parsed from JSON text into a Ruby object. Pass raw: true to get the unparsed JSON string back instead (no JSON.parse).

Examples:

redis.json_get("doc")
  # => { "a" => 1, "nested" => { "b" => 2 } }

raw JSON string

redis.json_get("doc", raw: true)
  # => '{"a":1,"nested":{"b":2}}'

Parameters:

  • key (String)
  • paths (Array<String>)

    zero or more JSONPath expressions; with none the whole document is returned

  • raw (Boolean)

    return the unparsed JSON string instead of a parsed Ruby object

Returns:

  • (Object, String, nil)

    the parsed JSON value (or the raw JSON string when raw is true), or nil if the key does not exist

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 136

def json_get(key, *paths, raw: false)
  send_command([:"JSON.GET", key, *paths]) do |reply|
    if reply.nil? || raw
      reply
    else
      ::JSON.parse(reply)
    end
  end
end

#json_merge(key, path, value, raw: false) ⇒ String

Merge value into the document stored under key at path, following RFC 7396 (JSON Merge Patch): a null value deletes a key, a non-null value creates or updates it, and any value merged into an existing array replaces the whole array. For a key that does not yet exist the path must be the root ("$").

By default value is a Ruby object serialized with JSON.generate; pass raw: true to send an already-encoded JSON string through untouched.

Examples:

redis.json_merge("doc", "$.b", 8)
  # => "OK"

Parameters:

  • key (String)
  • path (String)

    a JSONPath (must be "$" when creating a new key)

  • value (Object)

    a JSON-serializable Ruby object, or a pre-encoded JSON string when raw is true

  • raw (Boolean)

    treat value as an already-encoded JSON string and send it as-is

Returns:

  • (String)

    the raw "OK" reply

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 262

def json_merge(key, path, value, raw: false)
  value = ::JSON.generate(value) unless raw
  send_command([:"JSON.MERGE", key, path, value])
end

#json_mget(*keys, path, raw: false) ⇒ Array

Get the values at a single path from one or more keys.

Returns one element per key, in order, parsed from JSON text into a Ruby object (or nil when the key or path does not exist). Pass raw: true to get the unparsed JSON strings.

Examples:

redis.json_mget("doc1", "doc2", "$.a")
  # => [[1], [2]]

Parameters:

  • keys (Array<String>)

    one or more keys to read

  • path (String)

    a single JSONPath applied to every key

  • raw (Boolean)

    return the unparsed JSON strings instead of parsed Ruby objects

Returns:

  • (Array)

    one value per key (nil for a missing key/path)

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 185

def json_mget(*keys, path, raw: false)
  keys.flatten!(1)
  send_command([:"JSON.MGET", *keys, path]) do |reply|
    if reply.nil?
      reply
    else
      reply.map { |value| value.nil? || raw ? value : ::JSON.parse(value) }
    end
  end
end

#json_mset(*args, raw: false) ⇒ String

Set one or more JSON values atomically, one per key+/+path+/+value triplet. Either all of the writes are applied or none are. For a key that does not yet exist the path must be the root ("$").

By default each value is a Ruby object serialized with JSON.generate; pass raw: true to send already-encoded JSON strings through untouched.

Examples:

redis.json_mset("doc1", "$", { "a" => 1 }, "doc2", "$", { "b" => 2 })
  # => "OK"

Parameters:

  • args (Array)

    a flat list of key, path, value triplets

  • raw (Boolean)

    treat each value as an already-encoded JSON string

Returns:

  • (String)

    the raw "OK" reply

Raises:

  • (ArgumentError)

    unless args is a non-empty list of complete triplets

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 161

def json_mset(*args, raw: false)
  raise ArgumentError, "wrong number of arguments (expected key/path/value triplets)" \
    if args.empty? || !(args.size % 3).zero?

  command = [:"JSON.MSET"]
  args.each_slice(3) do |key, path, value|
    command << key << path << (raw ? value : ::JSON.generate(value))
  end
  send_command(command)
end

#json_numincrby(key, path, number) ⇒ Array<Numeric>, ...

Increment the numeric value(s) at path in the document stored under key by number.

Examples:

redis.json_numincrby("doc", "$.a", 2)
  # => [3]

Parameters:

  • key (String)
  • path (String)

    a JSONPath to the numeric value(s)

  • number (Numeric)

    the amount to add

Returns:

  • (Array<Numeric>, Numeric, nil)

    the new value(s): an Array for a JSONPath, a single number for a legacy path; nil (or nil element) for a non-numeric match

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 412

def json_numincrby(key, path, number)
  jsonpath = json_path?(path)
  send_command([:"JSON.NUMINCRBY", key, path, number]) do |reply|
    NumincrbyNormalize.call(reply, jsonpath)
  end
end

#json_objkeys(key, path = nil) ⇒ Array

Return the key names of the JSON object(s) at path. When path is omitted it defaults to the root.

Examples:

redis.json_objkeys("doc", "$.nested")
  # => [["b", "c"]]

Parameters:

  • key (String)
  • path (String) (defaults to: nil)

    an optional JSONPath (defaults to the root)

Returns:

  • (Array)

    an array of key-name arrays (JSONPath) or a single array of key names (legacy path); nil for a non-object match

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 450

def json_objkeys(key, path = nil)
  args = [:"JSON.OBJKEYS", key]
  args << path if path
  send_command(args)
end

#json_objlen(key, path = nil) ⇒ Array<Integer>, ...

Return the number of keys in the JSON object(s) at path. When path is omitted it defaults to the root.

Examples:

redis.json_objlen("doc", "$.nested")
  # => [2]

Parameters:

  • key (String)
  • path (String) (defaults to: nil)

    an optional JSONPath (defaults to the root)

Returns:

  • (Array<Integer>, Integer, nil)

    the key count(s): an Array for a JSONPath, a single integer for a legacy path; nil for a non-object match

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 467

def json_objlen(key, path = nil)
  args = [:"JSON.OBJLEN", key]
  args << path if path
  send_command(args)
end

#json_path?(path) ⇒ Boolean (private)

RedisJSON distinguishes JSONPath expressions (which start with "$") from legacy paths. JSONPath queries return an array of matches; legacy paths return a single value.

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 545

def json_path?(path)
  path.to_s.start_with?("$")
end

#json_set(key, path, value, nx: false, xx: false, raw: false, fpha: nil) ⇒ Boolean, String

Set the JSON value at path in the document stored under key.

By default value is a Ruby object that is serialized to JSON text with JSON.generate. Pass raw: true to send an already-encoded JSON string through untouched (no re-serialization), which avoids double-encoding pre-built JSON.

Examples:

redis.json_set("doc", "$", { "a" => 1, "nested" => { "b" => 2 } })
  # => "OK"

pre-encoded JSON

redis.json_set("doc", "$", '{"a":1}', raw: true)
  # => "OK"

conditional set with NX/XX returns a boolean

redis.json_set("doc", "$.a", 2, nx: true)
  # => false

Parameters:

  • key (String)
  • path (String)

    a JSONPath, e.g. "$" for the document root

  • value (Object)

    a JSON-serializable Ruby object, or a pre-encoded JSON string when raw is true

  • nx (Boolean)

    only set when the path does not already exist

  • xx (Boolean)

    only set when the path already exists

  • raw (Boolean)

    treat value as an already-encoded JSON string and send it as-is

  • fpha (String, Symbol)

    store a numeric array as a Floating-Point Homogeneous Array of the given precision — one of :bf16, :fp16, :fp32, :fp64 (case-insensitive; ::Redis 8.8+). All values of the array are forced into the fixed floating-point type: :bf16+/+:fp16 halve memory at reduced precision, while :fp32+/+:fp64 keep higher precision.

Returns:

  • (Boolean, String)

    when nx or xx is given, true on success and false when the condition was not met; otherwise the raw "OK" reply

Raises:

  • (ArgumentError)

    if both nx and xx are given (they are mutually exclusive), or if fpha is not one of the supported types

  • (Redis::CommandError)

    if a value in the array does not fit the chosen fpha type ("value out of range for ...")

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 94

def json_set(key, path, value, nx: false, xx: false, raw: false, fpha: nil)
  raise ArgumentError, "nx and xx are mutually exclusive" if nx && xx

  if fpha
    fpha = fpha.to_s.upcase
    unless JSON_SET_FPHA_TYPES.include?(fpha)
      raise ArgumentError, "fpha accepts only: #{JSON_SET_FPHA_TYPES.join(', ')}"
    end
  end

  value = ::JSON.generate(value) unless raw
  args = [:"JSON.SET", key, path, value]
  args << "NX" if nx
  args << "XX" if xx
  args << "FPHA" << fpha if fpha

  if nx || xx
    send_command(args, &BoolifySet)
  else
    send_command(args)
  end
end

#json_strappend(key, path, value, raw: false) ⇒ Array<Integer>, ...

Append value to the JSON string(s) at path in the document stored under key.

By default value is a Ruby string serialized with JSON.generate; pass raw: true to send an already-encoded JSON string through untouched.

Examples:

redis.json_strappend("doc", "$.a", "bar")
  # => [6]

Parameters:

  • key (String)
  • path (String)

    a JSONPath to the target string(s)

  • value (String)

    the string to append

  • raw (Boolean)

    treat value as an already-encoded JSON string and send it as-is

Returns:

  • (Array<Integer>, Integer, nil)

    the new string length(s): an Array for a JSONPath, a single integer for a legacy path; nil for a non-string match

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 505

def json_strappend(key, path, value, raw: false)
  value = ::JSON.generate(value) unless raw
  send_command([:"JSON.STRAPPEND", key, path, value])
end

#json_strlen(key, path = nil) ⇒ Array<Integer>, ...

Return the length of the JSON string(s) at path. When path is omitted it defaults to the root.

Examples:

redis.json_strlen("doc", "$.a")
  # => [3]

Parameters:

  • key (String)
  • path (String) (defaults to: nil)

    an optional JSONPath (defaults to the root)

Returns:

  • (Array<Integer>, Integer, nil)

    the string length(s): an Array for a JSONPath, a single integer for a legacy path; nil for a non-string match

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 484

def json_strlen(key, path = nil)
  args = [:"JSON.STRLEN", key]
  args << path if path
  send_command(args)
end

#json_toggle(key, path) ⇒ Array, ...

Toggle the boolean value(s) at path in the document stored under key.

Examples:

redis.json_toggle("doc", "$.flag")
  # => [0]

Parameters:

  • key (String)
  • path (String)

    a JSONPath to the target boolean(s)

Returns:

  • (Array, Object, nil)

    1+/+0 for the new true/false value(s): an Array for a JSONPath, a single value for a legacy path; nil for a non-boolean match

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 520

def json_toggle(key, path)
  send_command([:"JSON.TOGGLE", key, path])
end

#json_type(key, path = nil) ⇒ Array<String>, ...

Return the type name of the value(s) at path (e.g. "integer", "string", "object"). When path is omitted it defaults to the root.

Examples:

redis.json_type("doc", "$.a")
  # => ["integer"]

Parameters:

  • key (String)
  • path (String) (defaults to: nil)

    an optional JSONPath (defaults to the root)

Returns:

  • (Array<String>, String, nil)

    the type name(s): an Array for a JSONPath, a single string for a legacy path

[ GitHub ]

  
# File 'lib/redis/commands/modules/json.rb', line 430

def json_type(key, path = nil)
  jsonpath = json_path?(path)
  args = [:"JSON.TYPE", key]
  args << path if path
  send_command(args) do |reply|
    TypeNormalize.call(reply, jsonpath)
  end
end