Skip to content

Commit

Permalink
Adding unit tests, addressing changes to README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
tishun committed Sep 11, 2024
1 parent 5e5b2a3 commit 6257b35
Show file tree
Hide file tree
Showing 14 changed files with 841 additions and 136 deletions.
50 changes: 11 additions & 39 deletions src/main/java/io/lettuce/core/json/DelegateJsonArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import io.lettuce.core.codec.RedisCodec;
import io.lettuce.core.internal.LettuceAssert;

import java.util.ArrayList;
import java.util.Iterator;
Expand All @@ -33,14 +34,21 @@ class DelegateJsonArray extends DelegateJsonValue implements JsonArray {

@Override
public JsonArray add(JsonValue element) {
JsonNode newNode = ((DelegateJsonValue) element).getNode();
JsonNode newNode = null;

if (element != null) {
newNode = ((DelegateJsonValue) element).getNode();
}

((ArrayNode) node).add(newNode);

return this;
}

@Override
public void addAll(JsonArray element) {
LettuceAssert.notNull(element, "Element must not be null");

ArrayNode otherArray = (ArrayNode) ((DelegateJsonValue) element).getNode();
((ArrayNode) node).addAll(otherArray);
}
Expand All @@ -60,7 +68,7 @@ public List<JsonValue> asList() {
public JsonValue get(int index) {
JsonNode jsonNode = node.get(index);

return new DelegateJsonValue(jsonNode);
return jsonNode == null ? null : new DelegateJsonValue(jsonNode);
}

@Override
Expand All @@ -70,13 +78,7 @@ public JsonValue getFirst() {

@Override
public Iterator<JsonValue> iterator() {
List<JsonValue> result = new ArrayList<>();
while (node.iterator().hasNext()) {
JsonNode jsonNode = node.iterator().next();
result.add(new DelegateJsonValue(jsonNode));
}

return result.iterator();
return asList().iterator();
}

@Override
Expand All @@ -99,39 +101,9 @@ public int size() {
return node.size();
}

@Override
public boolean isJsonArray() {
return true;
}

@Override
public JsonArray asJsonArray() {
return this;
}

@Override
public boolean isString() {
return false;
}

@Override
public String asString() {
throw new UnsupportedOperationException("The JSON value is not a string");
}

@Override
public boolean isNumber() {
return false;
}

@Override
public Number asNumber() {
throw new UnsupportedOperationException("The JSON value is not a number");
}

@Override
public boolean isNull() {
return false;
}

}
34 changes: 2 additions & 32 deletions src/main/java/io/lettuce/core/json/DelegateJsonObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import io.lettuce.core.codec.RedisCodec;

/**
* Implementation of the {@link DelegateJsonObject} that delegates most of it's dunctionality to the Jackson {@link ObjectNode}.
* Implementation of the {@link DelegateJsonObject} that delegates most of its functionality to the Jackson {@link ObjectNode}.
*
* @author Tihomir Mateev
*/
Expand All @@ -39,7 +39,7 @@ public JsonObject put(String key, JsonValue element) {
public JsonValue get(String key) {
JsonNode value = node.get(key);

return new DelegateJsonValue(value);
return value == null ? null : new DelegateJsonValue(value);
}

@Override
Expand All @@ -54,39 +54,9 @@ public int size() {
return node.size();
}

@Override
public boolean isJsonObject() {
return true;
}

@Override
public JsonObject asJsonObject() {
return this;
}

@Override
public boolean isString() {
return false;
}

@Override
public String asString() {
throw new UnsupportedOperationException("The JSON value is not a string");
}

@Override
public boolean isNumber() {
return false;
}

@Override
public Number asNumber() {
throw new UnsupportedOperationException("The JSON value is not a number");
}

@Override
public boolean isNull() {
return false;
}

}
31 changes: 20 additions & 11 deletions src/main/java/io/lettuce/core/json/DelegateJsonValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import java.nio.ByteBuffer;

/**
* Implementation of the {@link JsonValue} that delegates most of it's dunctionality to the Jackson {@link JsonNode}.
* Implementation of the {@link JsonValue} that delegates most of its functionality to the Jackson {@link JsonNode}.
*
* @author Tihomir Mateev
*/
Expand All @@ -39,22 +39,22 @@ public ByteBuffer asByteBuffer() {

@Override
public boolean isJsonArray() {
return false;
return node.isArray();
}

@Override
public JsonArray asJsonArray() {
throw new UnsupportedOperationException("The JSON value is not an array");
return null;
}

@Override
public boolean isJsonObject() {
return false;
return node.isObject();
}

@Override
public JsonObject asJsonObject() {
throw new UnsupportedOperationException("The JSON value is not an object");
return null;
}

@Override
Expand All @@ -64,26 +64,35 @@ public boolean isString() {

@Override
public String asString() {
return node.asText();
return node.isTextual() ? node.asText() : null;
}

@Override
public boolean isNumber() {
return node.isNumber();
}

@Override
public Boolean asBoolean() {

return node.isBoolean() ? node.asBoolean() : null;
}

@Override
public boolean isBoolean() {
return node.isBoolean();
}

public boolean isNull() {
return node.isNull();
}

@Override
public Number asNumber() {
if (node.isInt()) {
return node.asInt();
} else if (node.isLong()) {
return node.asLong();
if (node.isNull()) {
return null;
}
return node.asDouble();
return node.numberValue();
}

protected JsonNode getNode() {
Expand Down
19 changes: 15 additions & 4 deletions src/main/java/io/lettuce/core/json/JsonValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public interface JsonValue {
boolean isJsonArray();

/**
* @return the {@link JsonArray} representation of this {@link JsonValue}
* @return the {@link JsonArray} representation of this {@link JsonValue}, null if this is not a JSON array
* @see #isJsonArray()
*/
JsonArray asJsonArray();
Expand All @@ -56,7 +56,7 @@ public interface JsonValue {
boolean isJsonObject();

/**
* @return the {@link JsonObject} representation of this {@link JsonValue}
* @return the {@link JsonObject} representation of this {@link JsonValue}, null if this is not a JSON object
* @see #isJsonObject()
*/
JsonObject asJsonObject();
Expand All @@ -67,7 +67,7 @@ public interface JsonValue {
boolean isString();

/**
* @return the {@link String} representation of this {@link JsonValue}
* @return the {@link String} representation of this {@link JsonValue}, null if this is not a JSON string
* @see #isString()
*/
String asString();
Expand All @@ -78,11 +78,22 @@ public interface JsonValue {
boolean isNumber();

/**
* @return the {@link Number} representation of this {@link JsonValue}
* @return the {@link Number} representation of this {@link JsonValue}, null if this is not a JSON number
* @see #isNumber()
*/
Number asNumber();

/**
* @return {@code true} if this {@link JsonValue} represents a JSON boolean value
*/
boolean isBoolean();

/**
* @return the {@link Boolean} representation of this {@link JsonValue}, null if this is not a JSON boolean value
* @see #isNumber()
*/
Boolean asBoolean();

/**
* @return {@code true} if this {@link JsonValue} represents the value of null
*/
Expand Down
79 changes: 45 additions & 34 deletions src/main/java/io/lettuce/core/json/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
Lettuce supports [RedisJSON](https://oss.redis.com/redisjson/) starting from [Lettuce 6.5.0.RELEASE](https://github.com/redis/lettuce/releases/tag/6.5.0.RELEASE).

The driver generally allows three distinct ways of working with the RedisJSON module:
* (Default mode) - default JSON parsing using GSON behind the scenes
* (Advanced mode) - custom JSON parsing using a user-provided JSON parser library
* (Power-user mode) - unprocessed JSON documents based on the Codec infrastructure
* (Default mode) - default JSON parsing using Jackson behind the scenes
* (Advanced mode) - custom JSON parsing using a user-provided JSON parser
* (Power-user mode) - unprocessed JSON documents that have not gone through any process of deserialization or serialization

> [!IMPORTANT]\
> In all of the above modes the driver would refrain from processing the JSON document in the main event loop and instead
> In all the above modes, the driver would refrain from processing the JSON document in the main event loop and instead
delegate this to the user thread. This behaviour is consistent when both receiving and sending JSON documents - when
receiving the parsing is done lazily whenever a method is called that requires the JSON to be parsed; when sending the
JSON is serialized immediately after it is passed to any of the commands, but before dispatching the command to the
Expand All @@ -19,59 +19,70 @@ event loop.
Best for:
* Most typical use-cases where the JSON document is parsed and processed

### Example usage:

```java
RedisURI redisURI = RedisURI.Builder.redis("acme.com").build();
RedisClient redisClient = RedisClient.create(redisURI);
try(StatefulRedisConnection<ByteBuffer, ByteBuffer> connect = redisClient.connect()){
try (StatefulRedisConnection<ByteBuffer, ByteBuffer> connect = redisClient.connect()){
redis = connect.async();
JsonPath path = JsonPath.of("$..mountain_bikes[0:2].model");

JsonParser<String, String> parser = redis.getStatefulConnection().getJsonParser();
JsonObject<String, String> bikeRecord = parser.createEmptyJsonObject();
JsonObject<String, String> bikeSpecs = parser.createEmptyJsonObject();
JsonArray<String, String> bikeColors = parser.createEmptyJsonArray();

bikeSpecs.put("material", parser.createJsonValue("\"composite\""));
bikeSpecs.put("weight", parser.createJsonValue("11"));

bikeColors.add(parser.createJsonValue("\"yellow\""));
bikeColors.add(parser.createJsonValue("\"orange\""));

bikeRecord.put("id", parser.createJsonValue("\"bike:43\""));
bikeRecord.put("model", parser.createJsonValue("\"DesertFox\""));
bikeRecord.put("description", parser.createJsonValue("\"The DesertFox is a versatile bike for all terrains\""));
bikeRecord.put("price", parser.createJsonValue("\"1299\""));
JsonParser parser = redis.getJsonParser();
JsonObject bikeRecord = parser.createJsonObject();
JsonObject bikeSpecs = parser.createJsonObject();
JsonArray bikeColors = parser.createJsonArray();

bikeRecord = parser.createJsonObject();
bikeSpecs = parser.createJsonObject();
bikeColors = parser.createJsonArray();
bikeSpecs.put("material", parser.createJsonValue("\"wood\""));
bikeSpecs.put("weight", parser.createJsonValue("19"));
bikeColors.add(parser.createJsonValue("\"walnut\""));
bikeColors.add(parser.createJsonValue("\"chestnut\""));
bikeRecord.put("id", parser.createJsonValue("\"bike:13\""));
bikeRecord.put("model", parser.createJsonValue("\"Woody\""));
bikeRecord.put("description", parser.createJsonValue("\"The Woody is an environmentally-friendly wooden bike\""));
bikeRecord.put("price", parser.createJsonValue("\"1112\""));
bikeRecord.put("specs", bikeSpecs);
bikeRecord.put("colors", bikeColors);

JsonSetArgs args = JsonSetArgs.Builder.none();

String result = redis.jsonSet("bikes:inventory", path, bikeRecord, args).get();

String result = redis.jsonSet("bikes:inventory", path, bikeRecord).get();
}
```

## Advanced mode
Best for:
* Applications that want to handle parsing manually - either by using another library or by implementing their own parser

### Example usage:

```java
RedisURI redisURI = RedisURI.Builder.redis("127.0.0.1").withPort(16379).build();

try (RedisClient client = RedisClient.create(redisURI)) {
client.setOptions(ClientOptions.builder().jsonParser(new CustomParser()).build());
StatefulRedisConnection<String, String> connection = client.connect(StringCodec.UTF8);
RedisCommands<String, String> redis = connection.sync();
}
```

## Power-user mode
Best for:
* Applications that do little to no processing on the Java layer
* Applications that require that a specific custom RedisCodec be used

Example usage:
### Example usage:

```java
RedisURI redisURI = RedisURI.Builder.redis("acme.com").build();
RedisClient redisClient = RedisClient.create(redisURI);
try(StatefulRedisConnection<ByteBuffer, ByteBuffer> connect = redisClient.connect(new ByteBufferCodec())){
redis = connect.async();
JsonPath path = JsonPath.of("$..mountain_bikes[0:2].model");
JsonPath myPath = JsonPath.of("$..mountain_bikes");
RedisURI redisURI = RedisURI.Builder.redis("127.0.0.1").withPort(16379).build();
try (RedisClient client = RedisClient.create(redisURI)) {
RedisAsyncCommands<String, String> redis = client.connect().async();
RedisFuture<List<JsonValue>> bikes = redis.jsonGet("bikes:inventory", myPath);

List<JsonValue<ByteBuffer>> value = redis.jsonGet(BIKES_INVENTORY, JsonGetArgs.Builder.none(), path).get();
return value;
}
CompletionStage<RedisFuture<String>> stage = bikes.thenApply(
fetchedBikes -> redis.jsonSet("service_bikes", JsonPath.ROOT_PATH, fetchedBikes.get(0)));

String result = stage.toCompletableFuture().get().get();
}
```
Loading

0 comments on commit 6257b35

Please sign in to comment.