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