RedisJSON

Standard Redis is a flat key-value store. While you have Hashes, you cannot easily nest data or query deep into a structure without retrieving the entire object. RedisJSON solves this by adding support for hierarchical documents as a first-class data type.

1. What is RedisJSON?

RedisJSON transforms Redis into a high-performance document database. It allows you to store, update, and fetch JSON values from Redis keys.

The “Tree” Architecture

Unlike a simple SET key value where the value is a blob of text, RedisJSON parses your JSON and stores it as a binary tree structure in memory.

  • O(1) Access: Accessing a nested field like $.store.book[0].title does not require parsing the whole JSON string. Redis traverses the tree pointers directly to the target node.
  • Atomic Updates: You can modify a single number deep in the tree without touching the rest of the document.
  • Memory Efficient: Keys are stored once in a dictionary, reducing memory usage for documents with repetitive field names.
graph TD
  subgraph Memory_Layout [Redis Memory Layout]
  style Memory_Layout fill:var(--bg-main),stroke:var(--border-muted),color:var(--fg-default)

  Root((Root Key

user:1001))
  style Root fill:var(--accent-blue),stroke:var(--accent-blue),color:#ffffff

  Obj1{Object}
  style Obj1 fill:var(--accent-green),stroke:var(--accent-green),color:#ffffff

  Name(name: "Alice")
  style Name fill:var(--bg-subtle),stroke:var(--border-muted),color:var(--fg-muted)

  Stats{stats}
  style Stats fill:var(--accent-green),stroke:var(--accent-green),color:#ffffff

  Login(login: 5)
  style Login fill:var(--bg-subtle),stroke:var(--border-muted),color:var(--fg-muted)

  Score(score: 98.5)
  style Score fill:var(--bg-subtle),stroke:var(--border-muted),color:var(--fg-muted)

  Root --> Obj1
  Obj1 --> Name
  Obj1 --> Stats
  Stats --> Login
  Stats --> Score
  end

2. Key Operations

The primary commands revolve around JSON.SET and JSON.GET, using JSONPath syntax to target specific nodes.

Java & Go Implementation

import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.json.Path2;
import org.json.JSONObject;

public class RedisJsonExample {
    public static void main(String[] args) {
        JedisPooled client = new JedisPooled("localhost", 6379);

        // 1. Store a JSON object
        JSONObject user = new JSONObject()
            .put("name", "Alice")
            .put("age", 30)
            .put("stats", new JSONObject().put("login", 5).put("score", 100));

        // JSON.SET user:1 $ '{"name":"Alice",...}'
        client.jsonSet("user:1", Path2.ROOT_PATH, user);

        // 2. Get a specific field
        // JSON.GET user:1 $.stats.login
        Object loginCount = client.jsonGet("user:1", Path2.of("$.stats.login"));
        System.out.println("Login Count: " + loginCount); // 5

        // 3. Atomic Increment
        // JSON.NUMINCRBY user:1 $.stats.login 1
        client.jsonToggle("user:1", Path2.of("$.stats.login"));
        // Note: Jedis method names vary, using generic command for clarity:
        // client.sendCommand(JsonCommand.NUMINCRBY, "user:1", "$.stats.login", "1");
    }
}
package main

import (
    "context"
    "fmt"
    "github.com/redis/go-redis/v9"
)

type User struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Stats Stats  `json:"stats"`
}

type Stats struct {
    Login int `json:"login"`
    Score int `json:"score"`
}

func main() {
    ctx := context.Background()
    rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})

    // 1. Store a JSON object
    user := User{
        Name: "Alice",
        Age:  30,
        Stats: Stats{Login: 5, Score: 100},
    }

    // JSON.SET user:1 $ ...
    err := rdb.JSONSet(ctx, "user:1", "$", user).Err()
    if err != nil {
        panic(err)
    }

    // 2. Get a specific field
    // JSON.GET user:1 $.stats.login
    val, _ := rdb.JSONGet(ctx, "user:1", "$.stats.login").Result()
    fmt.Println("Login Count:", val) // "[5]" (returns array of matches)

    // 3. Atomic Increment
    // JSON.NUMINCRBY user:1 $.stats.login 1
    rdb.JSONNumIncrBy(ctx, "user:1", "$.stats.login", 1)
}

3. Why use RedisJSON?

  1. Partial Updates: In standard Redis, updating a field in a large JSON string requires fetching the whole string (network cost), decoding it (CPU cost), modifying it, encoding it, and saving it back. With RedisJSON, you send JSON.SET key $.field value, and the server updates just that pointer in the tree.
  2. Atomicity: Updates to nested fields are atomic. Two clients can update different fields of the same JSON document simultaneously without overwriting each other’s changes.
  3. Type Safety: RedisJSON enforces JSON validity. You cannot insert malformed JSON.

4. Interactive: JSON Path Visualizer

Type a JSONPath query to see how Redis traverses the document to find your data.

  • $ is the root.
  • .store accesses the store object.
  • [0] accesses the first element of an array.
Document Structure

          
Query Result