Chapter 08 · Redis JSON and Document-Oriented Data

Choose Hashes vs JSON from Query Needs, Structure, Memory, Client Ergonomics, and Search

Choose Redis Hashes, JSON, or multiple keys from structure, query, memory, lifecycle, client, Search, and migration requirements rather than slogans.

Intermediate → Advanced170–205 minutesHash vs JSON vs many-keys capstoneRedis Open Source 8.10.1Free/local-firstLast reviewed: September 6, 2026

Learning outcomes

AtlasMart has three plausible representations for a small application record: one Redis Hash, one Redis JSON document, or several independent keys. None is universally superior. The correct choice follows the required structure, query patterns, mutation granularity, lifecycle, memory/cardinality, client ergonomics, security, and Search needs.

01

Compare Hash, JSON, and many-key models from concrete access patterns instead of feature slogans.

02

Measure bounded representative fixtures with MEMORY USAGE and payload evidence.

03

Choose JSON when nested structure/path mutation is valuable and Hash when flat field/value access is sufficient.

04

Recognize when many keys are justified by independent lifecycle/authorization/hotness and when they create cardinality overhead.

05

Design a migration/rollback decision with Search, TTL, schema, persistence, Cluster, and client consequences.

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. Begin with one workload, not one favorite data type

Consider an AtlasMart session/cart summary with customer ID, currency, item count, nested shipping address, tags, and a short TTL. The important questions are: which fields are read together, which mutate independently, whether nested arrays/objects matter, whether fields require Search, whether lifecycle differs, and how large/cardinal the data can become.

Need Hash JSON Many keys
flat field reads/writes excellent works, somewhat richer than needed works but more key overhead
nested object/array manual flatten/serialization native split relationships/application composition
single whole-record key TTL yes yes one TTL per key
independent sub-value key TTL not ordinary Hash fields on older versions; field expiry is version-specific no independent JSON-path key TTL yes
Search indexing supported supported with JSONPath schema usually index chosen source keys/structures
key cardinality one key one key many keys

2. Build equivalent small fixtures

The fixtures are intentionally small; their purpose is to make representation differences observable, not crown a universal winner.

redis-cli · JSON, Hash, and many-key representations
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch08:choice:json atlasmart:ch08:choice:hash atlasmart:ch08:choice:customer atlasmart:ch08:choice:itemCount atlasmart:ch08:choice:citydocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:choice:json '$' '{"customerId":77,"currency":"AZN","itemCount":2,"shipping":{"city":"Baku"},"tags":["mobile","returning"]}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch08:choice:hash customerId 77 currency AZN itemCount 2 shipping.city Baku tags mobile,returningdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch08:choice:customer 77docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch08:choice:itemCount 2docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch08:choice:city Baku

3. Compare direct reads

If a request needs only item count, both Hash and JSON can fetch a narrow value. Many keys can fetch it directly too.

redis-cli · one-value access
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:choice:json '$.itemCount'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HGET atlasmart:ch08:choice:hash itemCountdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GET atlasmart:ch08:choice:itemCount

Client ergonomics differ: JSON preserves numeric type on decode; Hash and String values are byte strings that the application parses according to its contract.

4. Compare structured reads

When nested shipping or arrays are first-class application structure, JSON communicates the shape directly. A Hash usually flattens names or stores serialized sub-values; many keys require application composition.

redis-cli · structured versus flattened evidence
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:choice:json '$.shipping' '$.tags'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HMGET atlasmart:ch08:choice:hash shipping.city tags

5. Compare mutation ergonomics

Atomic scalar mutations exist in all models, but at different levels. Hash fields and separate String keys use their native numeric commands; JSON uses typed path mutation.

redis-cli · increment the same logical field
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.NUMINCRBY atlasmart:ch08:choice:json '$.itemCount' 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HINCRBY atlasmart:ch08:choice:hash itemCount 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCR atlasmart:ch08:choice:itemCount

Do not choose solely from command brevity. Consider how the rest of the record is modeled and validated.

6. Compare memory carefully

Observe per-key memory, then remember that many-key total includes every key and its metadata.

redis-cli · memory observations
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:choice:jsondocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:choice:hashdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:choice:customerdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:choice:itemCountdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:choice:citydocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DBSIZE

Repeat at realistic cardinality and distributions. Small Hashes can use compact internal encodings; JSON has tree structure; many keys pay per-key overhead. These are implementation/capacity observations, not immutable API guarantees.

7. Lifecycle can outweigh structure

If the entire session expires together, one Hash or JSON key makes TTL policy simple. If city is persistent but a verification token must disappear in 30 seconds, storing them in the same JSON key couples lifecycle. A separate key may be justified for the short-lived sensitive value.

Lifecycle question Implication
all fields expire together single Hash/JSON key is operationally simple
one field has independent TTL separate key or verified field-expiration feature may fit better
one sub-value has different security policy separate key/service can align authorization boundary
unbounded child collection separate structure avoids giant parent key

8. Cardinality can make “many simple keys” expensive

Splitting ten fields for ten million entities into individual keys means up to one hundred million keys before auxiliary structures. That affects key metadata, scanning, operational discoverability, backups, expiration work, Cluster distribution, and monitoring. Estimate total key cardinality before choosing “one key per field” casually.

9. Search needs can favor either Hash or JSON

Redis Search can index Hash fields and JSON paths. JSON is not automatically the Search choice. Use JSON when source structure benefits from nesting/arrays/path operations; use Hash when a flat record is natural. Then build only the Search schema required by queries.

Important

Source representation and Search schema are related decisions, but not the same decision. A flat searchable record can remain a Hash; a nested document can be JSON and index only selected paths.

10. Client ergonomics and type contracts differ

Hash values arrive as byte/string fields and require application parsing. Redis JSON preserves JSON types and nested shape, but modern JSONPath APIs often return arrays of matches, which the client must decode correctly. Many keys can map cleanly to scalar APIs but require more round trips or pipelining when a logical record spans them.

Concern Hash JSON Many keys
numeric type application contract JSON numeric type application contract per key
nested decode manual native JSON application composition
partial read HGET/HMGET JSON.GET path(s) GET/MGET
multi-value response shape flat fields JSONPath multi-match aware list of key replies

11. Wrong approach: choose JSON because it looks modern

A flat five-field counter/config record may gain no value from nested JSON, while a nested catalog document may become awkward if forced into Hash flattening. The wrong criterion is aesthetics. The repair is to write down read/write/query/lifecycle/security/cardinality requirements first, then benchmark representative candidates.

12. Migration Hash → JSON has real compatibility work

A migration must decide key naming, field types, nested shape, TTL transfer, client cutover, Search source type/schema, rollback, and mixed-version reads. Simply copying strings into JSON can accidentally turn numeric fields into JSON strings.

redis-cli · type-aware migration sketch
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch08:migrate:hash sku M-1 stock 7docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:migrate:json '$' '{"schemaVersion":1,"sku":"M-1","stock":7}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.TYPE atlasmart:ch08:migrate:json '$.stock'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HGET atlasmart:ch08:migrate:hash stock

Validate counts and values before switching traffic; keep rollback until the source representation can be reconstructed or remains available.

13. Production decision matrix

Use this as a reasoning checklist, not an automatic scoring formula.

Choose... When this dominates Watch for
Hash flat compact record, field reads/writes, simple source model manual types/nesting; whole-hash access cost; version-sensitive field expiry
JSON bounded nested/array record, path reads/writes, JSON-native client model big documents, key-level lifecycle, schema drift, multi-match path semantics
Many keys independent lifecycle/security/hotness or separate scalar access key cardinality, network round trips, composition, scanning/operations
Search index secondary query requirements across records memory/write cost, schema migration, query/index visibility

14. Capstone comparison lab

Rebuild small equivalent fixtures, mutate/read them, record memory, and write your decision based on workload rather than the smallest byte count in one run.

redis-cli · comparison acceptance path
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch08:lesson5:json atlasmart:ch08:lesson5:hash atlasmart:ch08:lesson5:name atlasmart:ch08:lesson5:stockdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.SET atlasmart:ch08:lesson5:json '$' '{"name":"Cable","stock":4,"meta":{"color":"black"}}'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HSET atlasmart:ch08:lesson5:hash name Cable stock 4 meta.color blackdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch08:lesson5:name Cabledocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch08:lesson5:stock 4docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app JSON.GET atlasmart:ch08:lesson5:json '$.meta.color'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app HGET atlasmart:ch08:lesson5:hash meta.colordocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:lesson5:jsondocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:lesson5:hashdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:lesson5:namedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch08:lesson5:stock

Acceptance criteria: all representations expose the same logical values. Record the observed memory numbers, but justify your choice from structure/lifecycle/query/client needs too.

15. Cleanup and rollback discipline

Remove only Chapter 08 fixtures/indexes you created. Do not use FLUSHDB/FLUSHALL.

redis-cli · safe Chapter 08 cleanup
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin FT.DROPINDEX atlasmart-ch08-products-idxdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DEL atlasmart:ch08:product:1001 atlasmart:ch08:cart:77 atlasmart:ch08:product:2001 atlasmart:ch08:schema:v1 atlasmart:ch08:schema:v2 atlasmart:ch08:search:product:1 atlasmart:ch08:search:product:2 atlasmart:ch08:outside:product:3 atlasmart:ch08:compare:json atlasmart:ch08:compare:hash atlasmart:ch08:choice:json atlasmart:ch08:choice:hash atlasmart:ch08:choice:customer atlasmart:ch08:choice:itemCount atlasmart:ch08:choice:city atlasmart:ch08:migrate:hash atlasmart:ch08:migrate:json atlasmart:ch08:lesson5:json atlasmart:ch08:lesson5:hash atlasmart:ch08:lesson5:name atlasmart:ch08:lesson5:stock

An “unknown index” response is acceptable if Lesson 4 already dropped it. Cleanup remains prefix-specific and never touches unrelated data.

16. Production judgment

JSON is a strong choice for cohesive bounded nested records, not a default replacement for Hashes. Hashes remain excellent flat object-like structures; many keys are appropriate when lifecycle/authorization/hotness truly diverge. Measure realistic memory, payload, mutation latency, Search index cost, key cardinality, persistence/replication effects, and client behavior. Preserve rollback when changing representation.

17. Summary and bridge to Chapter 09

Chapter 08 established JSON as structured storage, not schema magic or automatic Search. You can now choose Hash versus JSON versus many keys based on evidence. Chapter 09 takes the Search side deeper: index scope, TEXT/TAG/NUMERIC/GEO semantics, query syntax, aggregation, profiling, and index cost.

Check your understanding

  1. When can Hash be a better source representation than JSON?
  2. When can many keys be justified despite key-cardinality overhead?
  3. Does JSON preserve numeric type more directly than Hash?
  4. Can Redis Search index both Hash and JSON sources?
  5. What must a Hash-to-JSON migration preserve besides values?
Review the answers

When the record is naturally flat and field-level access is sufficient.

When fields genuinely need independent lifecycle, authorization, hotness, or scalar access boundaries.

Yes. JSON has numeric values; Hash fields are bytes/strings interpreted by the application.

Yes. Source representation should be chosen from the data/workload, then Search schema from query needs.

Key naming, types, TTL/lifecycle, client compatibility, Search schema/source type, observability, and rollback.

Authoritative references

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.