Chapter 08 · Redis JSON and Document-Oriented Data

JSON.SET/GET and JSON Paths: Store and Address Nested Documents

Store and address nested Redis JSON documents with modern JSONPath semantics, explicit return-shape reasoning, and bounded AtlasMart modeling.

Intermediate150–180 minutesNested JSON path labRedis Open Source 8.10.1Free/local-firstLast reviewed: September 6, 2026

Learning outcomes

AtlasMart needs a product record whose dimensions, inventory summary, shipping settings, tags, and localized metadata belong together. Flattening every nested value into unrelated keys makes reads and lifecycle reasoning harder, while replacing a whole serialized blob for one nested change wastes network and write work. Redis JSON stores a JSON document under one Redis key and lets commands address values through JSONPath expressions.

01

Create and retrieve nested JSON documents with JSON.SET and JSON.GET.

02

Explain modern dollar-prefixed JSONPath versus legacy paths and their different return shapes.

03

Inspect JSON value types, object fields, arrays, and missing-path behavior without guessing.

04

Update a nested location without replacing the whole document and explain path-creation boundaries.

05

Keep JSON storage, Search indexing, schema validation, key TTL, and application authorization as separate concerns.

Exact lab baseline

All Chapter 08 mandatory labs reuse the disposable Chapter 01 environment: Redis Open Source 8.10.1 from Docker Official Image redis:8.10.1, container atlasmart-redis-ch01, standalone topology, host publication 127.0.0.1:6379, TLS disabled only because traffic stays on loopback, default ACL user disabled, named ACL users atlasmart-app and academy-admin, logical database 0, AOF with appendfsync everysec plus RDB snapshots, persistent /data volume, and no explicit Redis maxmemory limit or eviction policy. Redis 8 integrates JSON and Search capabilities in Redis Open Source, so the pinned image is the mandatory free/local path; no separate Redis Stack image or managed service is required. The primary client is the redis-cli shipped in the same image. Fixtures are bounded under atlasmart:ch08:*.

1. Redis key outside, JSON tree inside

A Redis JSON document still lives at one ordinary Redis key. The key participates in Redis keyspace behavior—ACL key patterns, expiration, persistence, replication, Cluster slotting—while the value is a typed JSON tree. A JSON path selects locations inside that tree; it is not another Redis key and cannot have an independent Redis key TTL merely because it is nested.

redis-cli · create one bounded AtlasMart document
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch08:product:1001docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:1001 '$' '{"schemaVersion":1,"sku":"SKU-1001","name":"Trail Backpack","priceCents":12990,"inventory":{"BAK-1":12,"TBS-1":4},"dimensions":{"weightGrams":870,"widthMm":320},"tags":["outdoor","travel"]}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:product:1001 '$'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TYPE atlasmart:ch08:product:1001

Expected evidence: JSON.SET returns OK; JSON.GET ... '$' returns a JSON-encoded array containing the root match; ordinary Redis TYPE reports the Redis JSON type rather than pretending the value is a String.

2. Modern JSONPath starts at dollar

In this course, modern JSONPath expressions start with $. Dot notation selects object members, brackets select array positions, * selects multiple children, filters select values by predicates, and recursive descent can search deeper trees. A modern path may match zero, one, or many locations, so commands often return an array-shaped result even when your particular fixture happens to have one match.

redis-cli · select nested and multi-match values
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:product:1001 '$.inventory.BAK-1'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:product:1001 '$.tags[*]'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:product:1001 '$..weightGrams'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.TYPE atlasmart:ch08:product:1001 '$.priceCents'

With the deterministic fixture, the first value is 12, the tags are outdoor and travel, and the type of priceCents is numeric/integer. Exact RESP2 versus RESP3 presentation can differ, so treat the semantic values—not terminal punctuation—as the invariant.

3. Legacy paths exist, but do not mix mental models

Redis JSON retains legacy path syntax for compatibility. Legacy paths generally resolve only the first matching location and several commands return a scalar or different nil/error shape instead of a modern JSONPath array. This is one reason copied examples from old RedisJSON tutorials can look inconsistent beside Redis 8 documentation.

Question Modern JSONPath Legacy path
Root form $ . or omitted
Multiple matches supported first matching location semantics
Typical return shape array/multi-match aware often scalar/single-match
Course recommendation preferred for new work use only when maintaining existing code

Do not “fix” an unexpected array by switching path syntaxes blindly. Choose one API contract, test it with your client library and RESP version, and decode it consistently.

4. Introspection proves what the tree actually contains

JSON documents are typed, but Redis JSON is not an application schema validator. Use introspection to observe stored state during development and operations.

redis-cli · type, object, and array evidence
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.TYPE atlasmart:ch08:product:1001 '$.inventory'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.OBJKEYS atlasmart:ch08:product:1001 '$.inventory'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.ARRLEN atlasmart:ch08:product:1001 '$.tags'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.TYPE atlasmart:ch08:product:1001 '$.missing'

The missing path should produce an empty modern-path result rather than inventing a value. OBJKEYS is O(N) in the object's keys, so it is useful evidence—not a free schema-discovery call to run repeatedly on giant objects.

5. JSON.SET can mutate a path without replacing the root

Once a path's parent exists, JSON.SET can replace that matched value or create a missing final object member. It cannot conjure a chain of missing intermediate objects.

redis-cli · partial nested update
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:1001 '$.dimensions.heightMm' '510'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:1001 '$.inventory.BAK-1' '11'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:product:1001 '$.dimensions' '$.inventory'

The document remains one key while only selected sub-elements change. This is the core difference from storing an opaque JSON string in a normal Redis String: Redis can address and mutate the tree itself.

6. Edge case: missing intermediate path

A common mistake is assuming Redis will create every missing object on a dotted path. It will not. If $.shipping does not exist, setting $.shipping.carrier cannot create the intermediate shipping object.

redis-cli · prove then repair missing-parent behavior
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:1001 '$.shipping.carrier' '"AZPost"'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:1001 '$.shipping' '{"carrier":"AZPost","fragile":false}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:product:1001 '$.shipping'

The first command returns nil because the intermediate parent is absent. Creating the object at $.shipping makes later child updates valid. This boundary should be covered by tests when schema versions add new nested containers.

7. NX and XX apply to path existence

NX writes only when the target path has no match; XX writes only when it already has a match. These are path-condition controls, not full schema or concurrency validation.

redis-cli · conditional document/path writes
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:1001 '$.rating' '4.6' NXdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:1001 '$.rating' '4.7' NXdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:1001 '$.rating' '4.7' XXdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:product:1001 '$.rating'

Use these guards when “create only” or “update only” is the actual invariant. They do not compare an expected old value; later chapters cover broader concurrency mechanisms.

8. Client-decoded value versus wire-level JSON string

JSON.GET returns serialized JSON, and client libraries often decode that into language-native arrays, objects, numbers, booleans, and nulls. To make the boundary concrete without requiring a third-party package, this Python example calls the pinned redis-cli --raw and decodes the JSON with the standard library.

python · decode a modern JSONPath root result
import jsonimport subprocesscmd = [    "docker", "exec", "-e", "REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026",    "atlasmart-redis-ch01", "redis-cli", "--raw",    "--user", "atlasmart-app",    "JSON.GET", "atlasmart:ch08:product:1001", "$"]raw = subprocess.check_output(cmd, text=True).strip()matches = json.loads(raw)doc = matches[0]print(doc["sku"], doc["inventory"]["BAK-1"], type(doc["priceCents"]).__name__)

This example makes two contracts visible: Redis returns serialized JSON, and the application chooses how to decode it. A production client may expose a different convenience API, so test its exact return types instead of assuming redis-cli formatting.

9. JSON is storage; Search is a separate secondary structure

Storing name, priceCents, or tags in JSON does not automatically create a Redis Search index. Direct key/path access works immediately. Querying “all products tagged outdoor under a price” requires an explicit Search index, taught in Lesson 4 and then deeply in Chapter 09.

Boundary

A JSON document is authoritative stored data for this lesson. A Search index is a derived access structure with its own schema, memory, write, visibility, and migration costs.

10. Wrong approach: use JSON as schema enforcement

Redis accepts valid JSON whose application-level shape differs from your intended schema. For example, one producer can write priceCents as a string while another writes an integer.

redis-cli · deliberate type drift then repair
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:1001 '$.priceCents' '"12990"'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.TYPE atlasmart:ch08:product:1001 '$.priceCents'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:product:1001 '$.priceCents' '12990'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.TYPE atlasmart:ch08:product:1001 '$.priceCents'

The repair is not “Redis JSON validates schema.” The repair is an application contract: validate on write, carry an explicit schema version, reject incompatible inputs, and test migrations.

11. Memory and size are properties to measure, not slogans

A nested JSON document has structural overhead and can become a big key if arrays or embedded objects grow without bound. Measure both Redis-key memory and serialized payload size.

redis-cli · bounded size evidence
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:product:1001docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin JSON.DEBUG MEMORY atlasmart:ch08:product:1001 '$'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:product:1001 '$'

MEMORY USAGE observes the whole Redis key. JSON.DEBUG MEMORY is debugging/introspection evidence and is not an application query primitive. Neither one creates a universal “JSON is larger/smaller than Hash” rule.

12. Reproducible lesson lab

Rebuild the fixture, prove path semantics, and leave the environment clean.

redis-cli · Chapter 08 Lesson 1 acceptance path
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch08:lesson1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:lesson1 '$' '{"schemaVersion":1,"customer":{"id":77,"name":"Leyla"},"items":[{"sku":"A","qty":1},{"sku":"B","qty":2}]}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:lesson1 '$.customer.name'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:lesson1 '$.items[*].sku'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.ARRLEN atlasmart:ch08:lesson1 '$.items'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:lesson1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch08:lesson1

Acceptance criteria: nested selection returns Leyla; the wildcard returns A and B; array length is 2; memory usage is a positive environment-specific value; cleanup removes only the lesson fixture.

13. Production judgment

Redis JSON is appropriate when a bounded record naturally contains nested/array structure and your workload benefits from path reads/writes. It does not provide schema validation, field-level key TTLs, relational constraints, or free arbitrary queries. Measure document size, mutation paths, persistence/replication cost, ACL scope, client decoding, Search indexing cost, and tail latency. In Cluster, the whole document belongs to the hash slot of its Redis key; nested paths do not distribute independently.

14. Summary and next step

You can now distinguish the Redis key from its JSON tree, use modern JSONPath intentionally, interpret return shapes, introspect structure, and perform bounded nested updates. Next we mutate numbers, strings, arrays, and objects atomically without replacing the whole root document.

Check your understanding

  1. Why does JSON.GET with a dollar path often return an array-shaped result?
  2. Can JSON.SET create an entire chain of missing intermediate objects?
  3. Does storing JSON automatically make its fields searchable with FT.SEARCH?
  4. What does Redis JSON validate for you?
  5. Why should you avoid mixing modern and legacy paths casually?
Review the answers

A modern JSONPath may match multiple locations, so the API is multi-match aware even when one location currently matches.

No. A missing final member can be created when its parent object exists; missing intermediate parents must be created first.

No. Search requires an explicit index and schema.

JSON syntax/types at storage operations, not your complete application schema or business invariants.

They have different matching and return/error/null semantics, which can silently change client behavior.

Authoritative references

  • Redis JSON data type — JSON storage, typed operations, and command overview
  • JSONPath syntax — modern dollar-path selection, wildcards, filters, slices, and recursive descent
  • JSON.SET — root/path writes plus NX/XX and path-creation rules
  • JSON.GET — serialized return shapes for modern and legacy paths
  • JSON.TYPE — type introspection and modern/legacy return behavior
  • JSON.OBJKEYS — object-key introspection and complexity
  • JSON.ARRLEN — array length and multi-match behavior
  • JSON.NUMINCRBY — atomic numeric path mutation
  • JSON.STRAPPEND — atomic string append and resulting lengths
  • JSON.ARRAPPEND — array append and resulting lengths
  • JSON.MERGE — RFC 7396 merge-patch behavior for objects and arrays
  • Index JSON documents — FT.CREATE ON JSON, JSONPath schema attributes, and indexing behavior
  • FT.CREATE — Search index schema, prefix scope, and field types
  • FT.INFO — index metadata and observable indexing state
  • MEMORY USAGE — per-key memory evidence and sampling
  • Redis 8.10 release notes — pinned server release family

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.