diff --git a/src/main/java/redis/clients/jedis/CommandObjects.java b/src/main/java/redis/clients/jedis/CommandObjects.java index 90fcafce26..886c82a1a6 100644 --- a/src/main/java/redis/clients/jedis/CommandObjects.java +++ b/src/main/java/redis/clients/jedis/CommandObjects.java @@ -3401,6 +3401,15 @@ public final CommandObject jsonSet(String key, Path path, Object pojo, J getJsonObjectMapper().toJson(pojo)).addParams(params), BuilderFactory.STRING); } + public final CommandObject jsonMerge(String key, Path2 path, Object object) { + return new CommandObject<>(commandArguments(JsonCommand.MERGE).key(key).add(path).add(object), BuilderFactory.STRING); + } + + public final CommandObject jsonMerge(String key, Path path, Object pojo) { + return new CommandObject<>(commandArguments(JsonCommand.MERGE).key(key).add(path).add( + getJsonObjectMapper().toJson(pojo)), BuilderFactory.STRING); + } + public final CommandObject jsonGet(String key) { return new CommandObject<>(commandArguments(JsonCommand.GET).key(key), new JsonObjectBuilder<>(Object.class)); } diff --git a/src/main/java/redis/clients/jedis/PipelineBase.java b/src/main/java/redis/clients/jedis/PipelineBase.java index dfd4eb9a45..0efa26d2f1 100644 --- a/src/main/java/redis/clients/jedis/PipelineBase.java +++ b/src/main/java/redis/clients/jedis/PipelineBase.java @@ -3458,6 +3458,16 @@ public Response jsonSet(String key, Path path, Object object, JsonSetPar return appendCommand(commandObjects.jsonSet(key, path, object, params)); } + @Override + public Response jsonMerge(String key, Path2 path, Object object) { + return appendCommand(commandObjects.jsonMerge(key, path, object)); + } + + @Override + public Response jsonMerge(String key, Path path, Object object) { + return appendCommand(commandObjects.jsonMerge(key, path, object)); + } + @Override public Response jsonGet(String key) { return appendCommand(commandObjects.jsonGet(key)); diff --git a/src/main/java/redis/clients/jedis/TransactionBase.java b/src/main/java/redis/clients/jedis/TransactionBase.java index f3aec7fcaf..86c8791733 100644 --- a/src/main/java/redis/clients/jedis/TransactionBase.java +++ b/src/main/java/redis/clients/jedis/TransactionBase.java @@ -53,7 +53,7 @@ public abstract class TransactionBase implements PipelineCommands, PipelineBinar /** * Creates a new transaction. - * + * * A MULTI command will be added to be sent to server. WATCH/UNWATCH/MULTI commands must not be * called with this object. * @param connection connection @@ -3626,6 +3626,16 @@ public Response jsonSet(String key, Path path, Object object, JsonSetPar return appendCommand(commandObjects.jsonSet(key, path, object, params)); } + @Override + public Response jsonMerge(String key, Path2 path, Object object) { + return appendCommand(commandObjects.jsonMerge(key, path, object)); + } + + @Override + public Response jsonMerge(String key, Path path, Object object) { + return appendCommand(commandObjects.jsonMerge(key, path, object)); + } + @Override public Response jsonGet(String key) { return appendCommand(commandObjects.jsonGet(key)); diff --git a/src/main/java/redis/clients/jedis/UnifiedJedis.java b/src/main/java/redis/clients/jedis/UnifiedJedis.java index c08b5f46db..ba3c014c1e 100644 --- a/src/main/java/redis/clients/jedis/UnifiedJedis.java +++ b/src/main/java/redis/clients/jedis/UnifiedJedis.java @@ -3902,6 +3902,16 @@ public String jsonSet(String key, Path path, Object pojo, JsonSetParams params) return executeCommand(commandObjects.jsonSet(key, path, pojo, params)); } + @Override + public String jsonMerge(String key, Path2 path, Object object) { + return executeCommand(commandObjects.jsonMerge(key, path, object)); + } + + @Override + public String jsonMerge(String key, Path path, Object pojo) { + return executeCommand(commandObjects.jsonMerge(key, path, pojo)); + } + @Override public Object jsonGet(String key) { return executeCommand(commandObjects.jsonGet(key)); diff --git a/src/main/java/redis/clients/jedis/json/JsonProtocol.java b/src/main/java/redis/clients/jedis/json/JsonProtocol.java index 1238ae37ff..c153c19f83 100644 --- a/src/main/java/redis/clients/jedis/json/JsonProtocol.java +++ b/src/main/java/redis/clients/jedis/json/JsonProtocol.java @@ -9,6 +9,7 @@ public enum JsonCommand implements ProtocolCommand { DEL("JSON.DEL"), GET("JSON.GET"), MGET("JSON.MGET"), + MERGE("JSON.MERGE"), SET("JSON.SET"), TYPE("JSON.TYPE"), STRAPPEND("JSON.STRAPPEND"), diff --git a/src/main/java/redis/clients/jedis/json/RedisJsonCommands.java b/src/main/java/redis/clients/jedis/json/RedisJsonCommands.java index 4567f889ba..c8c6e6ff43 100644 --- a/src/main/java/redis/clients/jedis/json/RedisJsonCommands.java +++ b/src/main/java/redis/clients/jedis/json/RedisJsonCommands.java @@ -43,6 +43,10 @@ default String jsonSetLegacy(String key, Object pojo, JsonSetParams params) { String jsonSet(String key, Path path, Object pojo, JsonSetParams params); + String jsonMerge(String key, Path2 path, Object object); + + String jsonMerge(String key, Path path, Object pojo); + Object jsonGet(String key); T jsonGet(String key, Class clazz); diff --git a/src/main/java/redis/clients/jedis/json/RedisJsonPipelineCommands.java b/src/main/java/redis/clients/jedis/json/RedisJsonPipelineCommands.java index e54373851f..e2668b3bc1 100644 --- a/src/main/java/redis/clients/jedis/json/RedisJsonPipelineCommands.java +++ b/src/main/java/redis/clients/jedis/json/RedisJsonPipelineCommands.java @@ -47,6 +47,10 @@ default Response jsonSetLegacy(String key, Object pojo, JsonSetParams pa Response jsonSet(String key, Path path, Object pojo, JsonSetParams params); + Response jsonMerge(String key, Path2 path, Object object); + + Response jsonMerge(String key, Path path, Object pojo); + Response jsonGet(String key); Response jsonGet(String key, Class clazz); diff --git a/src/test/java/redis/clients/jedis/modules/json/JsonObjects.java b/src/test/java/redis/clients/jedis/modules/json/JsonObjects.java index 00ab2e9f41..f605ca973f 100644 --- a/src/test/java/redis/clients/jedis/modules/json/JsonObjects.java +++ b/src/test/java/redis/clients/jedis/modules/json/JsonObjects.java @@ -1,6 +1,8 @@ package redis.clients.jedis.modules.json; import java.time.Instant; +import java.util.List; +import java.util.Map; import java.util.Objects; public class JsonObjects { @@ -102,15 +104,54 @@ public boolean equals(Object o) { } public static class Person { + public String name; + public int age; + public String address; + public String phone; + public List childrens; + + public Person(String name, int age, String address, String phone, List childrens) { + this.name = name; + this.age = age; + this.address = address; + this.phone = phone; + this.childrens = childrens; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + // if (getClass() != o.getClass()) { + // return false; + // } + Person other = (Person) o; + + return Objects.equals(name, other.name) + && Objects.equals(age, other.age) + && Objects.equals(address, other.address) + && Objects.equals(phone, other.phone) + && Objects.equals(childrens, other.childrens); + } + } + + public static class Tick { private final String id; private final Instant created; - public Person(String id, Instant created) { + + public Tick(String id, Instant created) { this.id = id; this.created = created; } + public String getId() { return id; } + public Instant getCreated() { return created; } diff --git a/src/test/java/redis/clients/jedis/modules/json/RedisJsonV1Test.java b/src/test/java/redis/clients/jedis/modules/json/RedisJsonV1Test.java index 391fb6e759..0449b1b26a 100644 --- a/src/test/java/redis/clients/jedis/modules/json/RedisJsonV1Test.java +++ b/src/test/java/redis/clients/jedis/modules/json/RedisJsonV1Test.java @@ -9,6 +9,7 @@ import com.google.gson.JsonObject; import java.time.Instant; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -186,6 +187,25 @@ public void typeChecksShouldSucceed() { assertNull(client.jsonType("foobar", Path.of(".fooErr"))); } + @Test + public void testJsonMerge() { + // create data + List childrens = new ArrayList<>(); + childrens.add("Child 1"); + Person person = new Person("John Doe", 25, "123 Main Street", "123-456-7890", childrens); + assertEquals("OK", client.jsonSet("test_merge", ROOT_PATH, person)); + + // After 5 years: + person.age = 30; + person.childrens.add("Child 2"); + person.childrens.add("Child 3"); + + // merge the new data + assertEquals("OK", client.jsonMerge("test_merge", Path.of((".childrens")), person.childrens)); + assertEquals("OK", client.jsonMerge("test_merge", Path.of((".age")), person.age)); + assertEquals(person, client.jsonGet("test_merge", Person.class)); + } + @Test public void mgetWithPathWithAllKeysExist() { Baz baz1 = new Baz("quuz1", "grault1", "waldo1"); @@ -501,7 +521,7 @@ public void resp() { @Test public void testJsonGsonParser() { - Person person = new Person("foo", Instant.now()); + Tick person = new Tick("foo", Instant.now()); // setting the custom json gson parser client.setJsonObjectMapper(JsonObjectMapperTestUtil.getCustomGsonObjectMapper()); @@ -514,7 +534,7 @@ public void testJsonGsonParser() { @Test public void testDefaultJsonGsonParserStringsMustBeDifferent() { - Person person = new Person("foo", Instant.now()); + Tick person = new Tick("foo", Instant.now()); // using the default json gson parser which is automatically configured @@ -526,7 +546,7 @@ public void testDefaultJsonGsonParserStringsMustBeDifferent() { @Test public void testJsonJacksonParser() { - Person person = new Person("foo", Instant.now()); + Tick person = new Tick("foo", Instant.now()); // setting the custom json jackson parser client.setJsonObjectMapper(JsonObjectMapperTestUtil.getCustomJacksonObjectMapper()); diff --git a/src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java b/src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java index f635f2bef1..def38f12a0 100644 --- a/src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java +++ b/src/test/java/redis/clients/jedis/modules/json/RedisJsonV2Test.java @@ -181,6 +181,57 @@ public void typeChecksShouldSucceed() { assertEquals(Collections.emptyList(), client.jsonType("foobar", Path2.of(".fooErr"))); } + @Test + public void testJsonMerge() { + // Test with root path + JSONObject json = new JSONObject("{\"person\":{\"name\":\"John Doe\",\"age\":25,\"address\":{\"home\":\"123 Main Street\"},\"phone\":\"123-456-7890\"}}"); + assertEquals("OK", client.jsonSet("test_merge", json)); + + json = new JSONObject("{\"person\":{\"name\":\"John Doe\",\"age\":30,\"address\":{\"home\":\"123 Main Street\"},\"phone\":\"123-456-7890\"}}"); + assertEquals("OK", client.jsonMerge("test_merge", Path2.of("$"), "{\"person\":{\"age\":30}}")); + + assertJsonArrayEquals(jsonArray(json), client.jsonGet("test_merge", Path2.of("$"))); + + // Test with root path path $.a.b + assertEquals("OK", client.jsonMerge("test_merge", Path2.of("$.person.address"), "{\"work\":\"Redis office\"}")); + json = new JSONObject("{\"person\":{\"name\":\"John Doe\",\"age\":30,\"address\":{\"home\":\"123 Main Street\",\"work\":\"Redis office\"},\"phone\":\"123-456-7890\"}}"); + assertJsonArrayEquals(jsonArray(json), client.jsonGet("test_merge", Path2.of("$"))); + + // Test with null value to delete a value + assertEquals("OK", client.jsonMerge("test_merge", Path2.of("$.person"), "{\"age\":null}")); + json = new JSONObject("{\"person\":{\"name\":\"John Doe\",\"address\":{\"home\":\"123 Main Street\",\"work\":\"Redis office\"},\"phone\":\"123-456-7890\"}}"); + assertJsonArrayEquals(jsonArray(json), client.jsonGet("test_merge", Path2.of("$"))); + + // cleanup + assertEquals(1L, client.del("test_merge")); + } + + @Test + public void testJsonMergeArray() + { + // Test merge on an array + JSONObject json = new JSONObject("{\"a\":{\"b\":{\"c\":[\"d\",\"e\"]}}}"); + assertEquals("OK", (client.jsonSet("test_merge_array", Path2.of("$"), json))); + assertEquals("OK", (client.jsonMerge("test_merge_array", Path2.of("$.a.b.c"), "[\"f\"]"))); + + json = new JSONObject("{\"a\":{\"b\":{\"c\":[\"f\"]}}}"); + assertJsonArrayEquals(jsonArray(json), client.jsonGet("test_merge_array", Path2.of("$"))); + + // assertEquals("{{a={b={c=[f]}}}", client.jsonGet("test_merge_array", Path2.of("$"))); + + // Test merge an array on a value + assertEquals("OK", (client.jsonSet("test_merge_array", Path2.of("$"), "{\"a\":{\"b\":{\"c\":\"d\"}}}"))); + assertEquals("OK", (client.jsonMerge("test_merge_array", Path2.of("$.a.b.c"), "[\"f\"]"))); + json = new JSONObject("{\"a\":{\"b\":{\"c\":[\"f\"]}}}"); + assertJsonArrayEquals(jsonArray(json), client.jsonGet("test_merge_array", Path2.of("$"))); + + // Test with null value to delete an array value + assertEquals("OK", (client.jsonSet("test_merge_array", Path2.of("$"), "{\"a\":{\"b\":{\"c\":[\"d\",\"e\"]}}}"))); + assertEquals("OK", (client.jsonMerge("test_merge_array", Path2.of("$.a.b"), "{\"c\":null}"))); + json = new JSONObject("{\"a\":{\"b\":{}}}"); + assertJsonArrayEquals(jsonArray(json), client.jsonGet("test_merge_array", Path2.of("$"))); + } + @Test public void mgetWithPathWithAllKeysExist() { Baz baz1 = new Baz("quuz1", "grault1", "waldo1");