Redis

Storing Nested JSON Without Re-Serialization Costs Using RedisJSON

June 9, 2026
6 min read

The classic Redis pattern for storing objects is to serialize them to JSON strings with SET and deserialize on GET. This works — until you need to update a single nested field. With a plain string you must fetch the entire document, deserialize, mutate, re-serialize, and write it back. RedisJSON eliminates that round-trip entirely.

The Problem with Serialized Strings

Consider a user profile stored as a JSON string:

# Old approach: full document round-trip to update one field
GET user:42
# -> '{"name":"Alice","email":"alice@example.com","settings":{"theme":"dark","notifications":true},"score":1500}'

# To update score: fetch -> parse -> mutate -> serialize -> write
SET user:42 '{"name":"Alice","email":"alice@example.com","settings":{"theme":"dark","notifications":true},"score":1501}'

With RedisJSON you update just the field you need — atomically, server-side, with no round-trip deserialization.

Storing a JSON Document

# JSON.SET key path value
# $ is the root path (JSONPath syntax)
JSON.SET user:42 $ '{"name":"Alice","email":"alice@example.com","settings":{"theme":"dark","notifications":true},"score":1500}'

# Retrieve the whole document
JSON.GET user:42

# Retrieve a nested field only
JSON.GET user:42 $.name
# -> ["Alice"]

JSON.GET user:42 $.settings.theme
# -> ["dark"]

Atomic Partial Updates

The key advantage: update deeply nested fields without touching the rest of the document.

# Update a single field (no fetch-deserialize-reserialize)
JSON.SET user:42 $.settings.theme '"light"'

# Increment a numeric field atomically
JSON.NUMINCRBY user:42 $.score 10
# -> [1510]

# Set a new nested field
JSON.SET user:42 $.settings.language '"en"'

# Retrieve multiple paths in one call
JSON.GET user:42 $.name $.score $.settings
# -> {"$.name":["Alice"],"$.score":[1510],"$.settings":[{"theme":"light","notifications":true,"language":"en"}]}

Working with Arrays

# Store a document with an array
JSON.SET product:10 $ '{"name":"Laptop","tags":["electronics","computers"],"reviews":[]}'

# Append to an array (no re-serialization)
JSON.ARRAPPEND product:10 $.tags '"sale"'
# -> [3]  (new array length)

JSON.ARRAPPEND product:10 $.reviews '{"user":"bob","rating":5,"text":"Great laptop!"}'

# Get array length
JSON.ARRLEN product:10 $.tags
# -> [3]

# Get a specific array element
JSON.GET product:10 $.tags[0]
# -> ["electronics"]

Multi-Key Operations

# Get the same path from multiple keys at once
JSON.MGET user:42 user:43 user:44 $.name
# -> [["Alice"], ["Bob"], ["Carol"]]

Python Integration

import redis
import json

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

def save_user(user_id, user_data):
    r.execute_command('JSON.SET', f'user:{user_id}', '$', json.dumps(user_data))

def get_user(user_id):
    result = r.execute_command('JSON.GET', f'user:{user_id}')
    return json.loads(result) if result else None

def update_user_field(user_id, path, value):
    # path example: "$.settings.theme"
    r.execute_command('JSON.SET', f'user:{user_id}', path, json.dumps(value))

def increment_score(user_id, delta=1):
    result = r.execute_command('JSON.NUMINCRBY', f'user:{user_id}', '$.score', delta)
    return json.loads(result)[0]

def add_tag(product_id, tag):
    r.execute_command('JSON.ARRAPPEND', f'product:{product_id}', '$.tags', json.dumps(tag))

# Usage
save_user(42, {"name": "Alice", "score": 1500, "settings": {"theme": "dark"}})
update_user_field(42, "$.settings.theme", "light")
new_score = increment_score(42, delta=10)
print(f"New score: {new_score}")  # 1510

Integration with RedisSearch

RedisJSON integrates natively with RedisSearch — you can index JSON document fields directly for full-text and numeric queries:

# Create a search index on JSON documents
FT.CREATE idx:users
  ON JSON
  PREFIX 1 user:
  SCHEMA
    $.name AS name TEXT
    $.email AS email TAG
    $.score AS score NUMERIC SORTABLE
    $.settings.theme AS theme TAG

# HSET is replaced by JSON.SET for document updates
# Search works identically to HASH-backed indexes
FT.SEARCH idx:users "@score:[1500 +inf]" SORTBY score DESC

Key Takeaways

  • JSON.SET stores a full document or updates any nested path using JSONPath syntax
  • JSON.GET retrieves the whole document or specific paths — multiple paths in one call
  • JSON.NUMINCRBY and JSON.ARRAPPEND mutate fields atomically server-side — no round-trip needed
  • JSON.MGET fetches the same path from multiple keys in a single command
  • RedisJSON documents are first-class citizens in RedisSearch — index JSON fields directly for full-text and numeric queries
  • All operations are atomic — concurrent updates to different paths of the same document are safe