Learn to store, read and search data in JSON documents using Jedis

Last updated 26, Apr 2024

Goal

Redis can manage JSON documents, and in addition to indexing Redis hashes, JSON documents can be indexed and searched. This document provides a full example of storing, indexing, and searching session data with Jedis.

Solution

In the example detailed in this document, we'll proceed to store session data in the following format:

[
   {
      "lastAccessedTime":1713903362,
      "creationTime":1713903362,
      "location":"34.638,31.79",
      "visited":[
         "www.redis.io",
         "www.wikipedia.com",
         "www.mortensi.com"
      ],
      "cart":[
         {
            "quantity":1,
            "price":1990.99,
            "id":"hp-2341"
         },
         {
            "quantity":2,
            "price":19.99,
            "id":"case-9993"
         }
      ]
   }
]

We will also explore how to create an index to index different parts of the sessions. Let's start and connect to Redis as follows:

HostAndPort node = HostAndPort.from("localhost:6379");
JedisClientConfig clientConfig = DefaultJedisClientConfig.builder()
                                        .resp3() 
                                        .build();

UnifiedJedis client = new UnifiedJedis(node, clientConfig);

Learn to set up a Maven project with Jedis

We are creating an index; let's drop an eventual index and all the associated documents.

client.ftDropIndexDD("session_idx");

If you would like to preserve the data and drop the index, use the command ftDropIndex

Now, we can define the index by choosing what fields require indexing.

Schema schema = new Schema()
                        .addGeoField("$.location").as("location")
                        .addTagField("$.cart[*].id").as("item_id")
                        .addNumericField("$.cart[*].price").as("price")
                        .addTagField("$.visited[*]").as("visited")
                        .addNumericField("$.lastAccessedTime").as("updated")
                        .addNumericField("$.creationTime").as("created");

// Defining the index, it could be HASH too
IndexDefinition def = new IndexDefinition(Type.JSON).setPrefixes(new String[] {"session:"});

// Creating the index 
client.ftCreate("session_idx", IndexOptions.defaultOptions().setDefinition(def), schema);

Now, let's introduce some metadata like session creation and update timestamps, as well as the user's location expressed as the pair (longitude, latitude). Learn more about indexing in Redis.

JSONObject jsonObject = new JSONObject();
jsonObject.put("lastAccessedTime", System.currentTimeMillis() / 1000L);
jsonObject.put("creationTime", System.currentTimeMillis() / 1000L);
jsonObject.put("location", "34.638,31.79");
client.jsonSet("session:1", jsonObject);

We can also store an array of visited URLs:

client.jsonSet("session:1", new Path2("visited"), new ArrayList<>());
List<String> visited = new ArrayList<String>();
visited.add("www.redis.io");
visited.add("www.wikipedia.com");
visited.add("www.mortensi.com");
client.jsonSetWithEscape("session:1", new Path2("visited"), visited);

Now we store a shopping cart for this user with a couple of items.

// Shopping cart item
JSONObject laptop = new JSONObject();
laptop.put("id", "hp-2341");
laptop.put("price", 1990.99);
laptop.put("quantity", 1);

// Another shopping cart item
JSONObject laptopCase = new JSONObject();
laptopCase.put("id", "case-9993");
laptopCase.put("price", 19.99);
laptopCase.put("quantity", 2);

// Storing items in the shopping cart
client.jsonSet("session:1", new Path2("cart"), new ArrayList<>());
client.jsonArrAppend("session:1", new Path2("cart"), laptop);
client.jsonArrAppend("session:1", new Path2("cart"), laptopCase);

Let's create a second session to demonstrate how we can perform cross-session search operations.

// Creating another session
JSONObject jsonObject2 = new JSONObject();
jsonObject2.put("lastAccessedTime", System.currentTimeMillis() / 1000L);
jsonObject2.put("creationTime", System.currentTimeMillis() / 1000L);
jsonObject2.put("location", "34.638,31.79");
client.jsonSet("session:2", jsonObject2);

// Shopping cart item
JSONObject book = new JSONObject();
book.put("id", "sking-2435");
book.put("price", 14.90);
book.put("quantity", 1);

client.jsonSet("session:2", new Path2("cart"), new ArrayList<>());
client.jsonArrAppend("session:2", new Path2("cart"), book);

Time to retrieve our data. Print an entire session on screen:

System.out.println(client.jsonGet("session:1"));

Read a part of the session, only the cart in this case:

System.out.println(client.jsonGet("session:1", new Path2("$.cart")));

You can also search within a session for a specific item by id and return the price. The search syntax follows the JSONPath syntax syntax.

System.out.println(client.jsonGet("session:1", new Path2("$.cart[?(@.id==\"hp-2341\")].price")));

Finally, let's use the session_idx index we have created and perform a cross-session search to retrieve those sessions having an item priced between 10 and 30 "units."

Query q = new Query().addFilter(new Query.NumericFilter("price", 10, 30)).setSortBy("price", true).setNoContent();
SearchResult res = client.ftSearch("session_idx", q);
System.out.println("Number of results: " + res.getTotalResults());
List<Document> docs = res.getDocuments();

for (Document doc : docs) {
        System.out.println("Found session: " + doc.getId()); 
}

The search will retrieve the following results, as both sessions have items in the price range.

Number of results: 2
Found session: session:2
Found session: session:1

References