diff --git a/src/NRedisStack/Json/DataTypes/KeyValuePath.cs b/src/NRedisStack/Json/DataTypes/KeyValuePath.cs
index 52d4009d..252b639e 100644
--- a/src/NRedisStack/Json/DataTypes/KeyValuePath.cs
+++ b/src/NRedisStack/Json/DataTypes/KeyValuePath.cs
@@ -2,22 +2,17 @@
namespace NRedisStack.Json.DataTypes;
-public struct KeyValuePath
+public struct KeyPathValue
{
public string Key { get; set; }
- public object Value { get; set; }
public string Path { get; set; }
+ public object Value { get; set; }
- public KeyValuePath(string key, object value, string path = "$")
+ public KeyPathValue(string key, string path, object value)
{
- if (key == null || value == null)
- {
- throw new ArgumentNullException("Key and value cannot be null.");
- }
-
Key = key;
- Value = value;
Path = path;
+ Value = value;
}
public string[] ToArray()
{
diff --git a/src/NRedisStack/Json/IJsonCommands.cs b/src/NRedisStack/Json/IJsonCommands.cs
index cc6fd3ee..bed3e52b 100644
--- a/src/NRedisStack/Json/IJsonCommands.cs
+++ b/src/NRedisStack/Json/IJsonCommands.cs
@@ -209,11 +209,31 @@ public interface IJsonCommands
///
/// Sets or updates the JSON value of one or more keys.
///
- /// The key, The value to set and
+ /// The key, The value to set and
/// The path to set within the key, must be > 1
/// The disposition of the command
///
- bool MSet(KeyValuePath[] keyValuePathList);
+ bool MSet(KeyPathValue[] KeyPathValueList);
+
+ ///
+ /// Sets or updates the JSON value at a path.
+ ///
+ /// The key.
+ /// The path to set within the key.
+ /// The value to set.
+ /// The disposition of the command
+ ///
+ bool Merge(RedisKey key, RedisValue path, RedisValue json);
+
+ ///
+ /// Sets or updates the JSON value at a path.
+ ///
+ /// The key.
+ /// The path to set within the key.
+ /// The value to set.
+ /// The disposition of the command
+ ///
+ bool Merge(RedisKey key, RedisValue path, object obj);
///
/// Sets or updates the JSON value of one or more keys.
@@ -242,7 +262,8 @@ public interface IJsonCommands
/// The key to append to.
/// The path of the string(s) to append to.
/// The value to append.
- /// The new length of the string(s) appended to, those lengths will be null if the path did not resolve ot a string.
+ /// The new length of the string(s) appended to, those lengths
+ /// will be null if the path did not resolve ot a string.
///
long?[] StrAppend(RedisKey key, string value, string? path = null);
@@ -251,7 +272,8 @@ public interface IJsonCommands
///
/// The key of the json object.
/// The path of the string(s) within the json object.
- /// The length of the string(s) appended to, those lengths will be null if the path did not resolve ot a string.
+ /// The length of the string(s) appended to, those lengths
+ /// will be null if the path did not resolve ot a string.
///
public long?[] StrLen(RedisKey key, string? path = null);
diff --git a/src/NRedisStack/Json/IJsonCommandsAsync.cs b/src/NRedisStack/Json/IJsonCommandsAsync.cs
index 2ad45a49..81c1343f 100644
--- a/src/NRedisStack/Json/IJsonCommandsAsync.cs
+++ b/src/NRedisStack/Json/IJsonCommandsAsync.cs
@@ -209,11 +209,31 @@ public interface IJsonCommandsAsync
///
/// Sets or updates the JSON value of one or more keys.
///
- /// The key, The value to set and
+ /// The key, The value to set and
/// The path to set within the key, must be > 1
/// The disposition of the command
///
- Task MSetAsync(KeyValuePath[] keyValuePathList);
+ Task MSetAsync(KeyPathValue[] KeyPathValueList);
+
+ ///
+ /// Sets or updates the JSON value at a path.
+ ///
+ /// The key.
+ /// The path to set within the key.
+ /// The value to set.
+ /// The disposition of the command
+ ///
+ Task MergeAsync(RedisKey key, RedisValue path, RedisValue json);
+
+ ///
+ /// Sets or updates the JSON value at a path.
+ ///
+ /// The key.
+ /// The path to set within the key.
+ /// The value to set.
+ /// The disposition of the command
+ ///
+ Task MergeAsync(RedisKey key, RedisValue path, object obj);
///
/// Set json file from the provided file Path.
diff --git a/src/NRedisStack/Json/JsonCommandBuilder.cs b/src/NRedisStack/Json/JsonCommandBuilder.cs
index 48211320..b5505100 100644
--- a/src/NRedisStack/Json/JsonCommandBuilder.cs
+++ b/src/NRedisStack/Json/JsonCommandBuilder.cs
@@ -29,15 +29,20 @@ public static SerializedCommand Set(RedisKey key, RedisValue path, RedisValue js
};
}
- public static SerializedCommand MSet(KeyValuePath[] keyValuePathList)
+ public static SerializedCommand MSet(KeyPathValue[] KeyPathValueList)
{
- if (keyValuePathList.Length < 1)
- throw new ArgumentOutOfRangeException(nameof(keyValuePathList));
+ if (KeyPathValueList.Length < 1)
+ throw new ArgumentOutOfRangeException(nameof(KeyPathValueList));
- var args = keyValuePathList.SelectMany(x => x.ToArray()).ToArray();
+ var args = KeyPathValueList.SelectMany(x => x.ToArray()).ToArray();
return new SerializedCommand(JSON.MSET, args);
}
+ public static SerializedCommand Merge(RedisKey key, RedisValue path, RedisValue json)
+ {
+ return new SerializedCommand(JSON.MERGE, key, path, json);
+ }
+
public static SerializedCommand StrAppend(RedisKey key, string value, string? path = null)
{
if (path == null)
diff --git a/src/NRedisStack/Json/JsonCommands.cs b/src/NRedisStack/Json/JsonCommands.cs
index 876db349..d1e588f7 100644
--- a/src/NRedisStack/Json/JsonCommands.cs
+++ b/src/NRedisStack/Json/JsonCommands.cs
@@ -41,9 +41,22 @@ public bool Set(RedisKey key, RedisValue path, RedisValue json, When when = When
}
///
- public bool MSet(KeyValuePath[] keyValuePathList)
+ public bool MSet(KeyPathValue[] KeyPathValueList)
{
- return _db.Execute(JsonCommandBuilder.MSet(keyValuePathList)).OKtoBoolean();
+ return _db.Execute(JsonCommandBuilder.MSet(KeyPathValueList)).OKtoBoolean();
+ }
+
+ ///
+ public bool Merge(RedisKey key, RedisValue path, RedisValue json)
+ {
+ return _db.Execute(JsonCommandBuilder.Merge(key, path, json)).OKtoBoolean();
+ }
+
+ ///
+ public bool Merge(RedisKey key, RedisValue path, object obj)
+ {
+ string json = JsonSerializer.Serialize(obj);
+ return _db.Execute(JsonCommandBuilder.Merge(key, path, json)).OKtoBoolean();
}
///
diff --git a/src/NRedisStack/Json/JsonCommandsAsync.cs b/src/NRedisStack/Json/JsonCommandsAsync.cs
index 3eb32026..c42fcbd5 100644
--- a/src/NRedisStack/Json/JsonCommandsAsync.cs
+++ b/src/NRedisStack/Json/JsonCommandsAsync.cs
@@ -144,9 +144,22 @@ public async Task SetAsync(RedisKey key, RedisValue path, RedisValue json,
return (await _db.ExecuteAsync(JsonCommandBuilder.Set(key, path, json, when))).OKtoBoolean();
}
- public async Task MSetAsync(KeyValuePath[] keyValuePathList)
+ public async Task MSetAsync(KeyPathValue[] KeyPathValueList)
{
- return (await _db.ExecuteAsync(JsonCommandBuilder.MSet(keyValuePathList))).OKtoBoolean();
+ return (await _db.ExecuteAsync(JsonCommandBuilder.MSet(KeyPathValueList))).OKtoBoolean();
+ }
+
+ ///
+ public async Task MergeAsync(RedisKey key, RedisValue path, RedisValue json)
+ {
+ return (await _db.ExecuteAsync(JsonCommandBuilder.Merge(key, path, json))).OKtoBoolean();
+ }
+
+ ///
+ public async Task MergeAsync(RedisKey key, RedisValue path, object obj)
+ {
+ string json = JsonSerializer.Serialize(obj);
+ return (await _db.ExecuteAsync(JsonCommandBuilder.Merge(key, path, json))).OKtoBoolean();
}
public async Task SetFromFileAsync(RedisKey key, RedisValue path, string filePath, When when = When.Always)
diff --git a/src/NRedisStack/Json/Literals/Commands.cs b/src/NRedisStack/Json/Literals/Commands.cs
index f748fae0..8ddbafe0 100644
--- a/src/NRedisStack/Json/Literals/Commands.cs
+++ b/src/NRedisStack/Json/Literals/Commands.cs
@@ -15,6 +15,7 @@ internal class JSON
public const string FORGET = "JSON.FORGET";
public const string GET = "JSON.GET";
public const string MEMORY = "MEMORY";
+ public const string MERGE = "JSON.MERGE";
public const string MSET = "JSON.MSET";
public const string MGET = "JSON.MGET";
public const string NUMINCRBY = "JSON.NUMINCRBY";
diff --git a/tests/NRedisStack.Tests/Json/JsonTests.cs b/tests/NRedisStack.Tests/Json/JsonTests.cs
index 59f17e11..18a66392 100644
--- a/tests/NRedisStack.Tests/Json/JsonTests.cs
+++ b/tests/NRedisStack.Tests/Json/JsonTests.cs
@@ -726,7 +726,7 @@ public async Task GetAsync()
}
[Fact]
- [Trait("Category","edge")]
+ [Trait("Category", "edge")]
public void MSet()
{
IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase());
@@ -734,10 +734,10 @@ public void MSet()
var key1 = keys[0];
var key2 = keys[1];
- KeyValuePath[] values = new[]
+ KeyPathValue[] values = new[]
{
- new KeyValuePath(key1, new { a = "hello" }),
- new KeyValuePath(key2, new { a = "world" })
+ new KeyPathValue(key1, "$", new { a = "hello" }),
+ new KeyPathValue(key2, "$", new { a = "world" })
};
commands.MSet(values)
;
@@ -747,22 +747,21 @@ public void MSet()
Assert.Equal("[\"world\"]", result[1].ToString());
// test errors:
- Assert.Throws(() => commands.MSet(new KeyValuePath[0]));
-
+ Assert.Throws(() => commands.MSet(new KeyPathValue[0]));
}
[Fact]
- [Trait("Category","edge")]
+ [Trait("Category", "edge")]
public async Task MSetAsync()
{
IJsonCommandsAsync commands = new JsonCommands(redisFixture.Redis.GetDatabase());
var keys = CreateKeyNames(2);
var key1 = keys[0];
var key2 = keys[1];
- KeyValuePath[] values = new[]
+ KeyPathValue[] values = new[]
{
- new KeyValuePath(key1, new { a = "hello" }),
- new KeyValuePath(key2, new { a = "world" })
+ new KeyPathValue(key1, "$", new { a = "hello" }),
+ new KeyPathValue(key2, "$", new { a = "world" })
};
await commands.MSetAsync(values)
;
@@ -772,14 +771,47 @@ await commands.MSetAsync(values)
Assert.Equal("[\"world\"]", result[1].ToString());
// test errors:
- await Assert.ThrowsAsync(async () => await commands.MSetAsync(new KeyValuePath[0]));
+ await Assert.ThrowsAsync(async () => await commands.MSetAsync(new KeyPathValue[0]));
}
[Fact]
- public void TestKeyValuePathErrors()
+ [Trait("Category", "edge")]
+ public void Merge()
{
- Assert.Throws(() => new KeyValuePath(null!, new { a = "hello" }));
- Assert.Throws(() => new KeyValuePath("key", null!) );
+ // Create a connection to Redis
+ IJsonCommands commands = new JsonCommands(redisFixture.Redis.GetDatabase());
+
+ Assert.True(commands.Set("test_merge", "$", new { person = new { name = "John Doe", age = 25, address = new {home = "123 Main Street"}, phone = "123-456-7890" } }));
+ Assert.True(commands.Merge("test_merge", "$", new { person = new { age = 30 } }));
+ Assert.Equal("{\"person\":{\"name\":\"John Doe\",\"age\":30,\"address\":{\"home\":\"123 Main Street\"},\"phone\":\"123-456-7890\"}}", commands.Get("test_merge").ToString());
+
+ // Test with root path path $.a.b
+ Assert.True(commands.Merge("test_merge", "$.person.address", new {work = "Redis office"}));
+ Assert.Equal("{\"person\":{\"name\":\"John Doe\",\"age\":30,\"address\":{\"home\":\"123 Main Street\",\"work\":\"Redis office\"},\"phone\":\"123-456-7890\"}}", commands.Get("test_merge").ToString());
+
+ // Test with null value to delete a value
+ Assert.True(commands.Merge("test_merge", "$.person", "{\"age\":null}"));
+ Assert.Equal("{\"person\":{\"name\":\"John Doe\",\"phone\":\"123-456-7890\",\"address\":{\"home\":\"123 Main Street\",\"work\":\"Redis office\"}}}", commands.Get("test_merge").ToString());
+ }
+
+ [Fact]
+ [Trait("Category", "edge")]
+ public async Task MergeAsync()
+ {
+ // Create a connection to Redis
+ IJsonCommandsAsync commands = new JsonCommands(redisFixture.Redis.GetDatabase());
+
+ Assert.True(await commands.SetAsync("test_merge", "$", new { person = new { name = "John Doe", age = 25, address = new {home = "123 Main Street"}, phone = "123-456-7890" } }));
+ Assert.True(await commands.MergeAsync("test_merge", "$", new { person = new { age = 30 } }));
+ Assert.Equal("{\"person\":{\"name\":\"John Doe\",\"age\":30,\"address\":{\"home\":\"123 Main Street\"},\"phone\":\"123-456-7890\"}}", (await commands.GetAsync("test_merge")).ToString());
+
+ // Test with root path path $.a.b
+ Assert.True(await commands.MergeAsync("test_merge", "$.person.address", new {work = "Redis office"}));
+ Assert.Equal("{\"person\":{\"name\":\"John Doe\",\"age\":30,\"address\":{\"home\":\"123 Main Street\",\"work\":\"Redis office\"},\"phone\":\"123-456-7890\"}}", (await commands.GetAsync("test_merge")).ToString());
+
+ // Test with null value to delete a value
+ Assert.True(await commands.MergeAsync("test_merge", "$.person", "{\"age\":null}"));
+ Assert.Equal("{\"person\":{\"name\":\"John Doe\",\"phone\":\"123-456-7890\",\"address\":{\"home\":\"123 Main Street\",\"work\":\"Redis office\"}}}", (await commands.GetAsync("test_merge")).ToString());
}
[Fact]
@@ -1067,18 +1099,18 @@ public async Task TestSetFromDirectoryAsync()
public void TestJsonCommandBuilder()
{
var getBuild1 = JsonCommandBuilder.Get("key", "indent", "newline", "space", "path");
- var getBuild2 = JsonCommandBuilder.Get("key",new string[]{"path1", "path2", "path3"}, "indent", "newline", "space");
- var expectedArgs1 = new object[] { "key", "INDENT", "indent", "NEWLINE","newline", "SPACE", "space", "path" };
+ var getBuild2 = JsonCommandBuilder.Get("key", new string[] { "path1", "path2", "path3" }, "indent", "newline", "space");
+ var expectedArgs1 = new object[] { "key", "INDENT", "indent", "NEWLINE", "newline", "SPACE", "space", "path" };
var expectedArgs2 = new object[] { "key", "INDENT", "indent", "NEWLINE", "newline", "SPACE", "space", "path1", "path2", "path3" };
- for(int i = 0; i < expectedArgs1.Length; i++)
+ for (int i = 0; i < expectedArgs1.Length; i++)
{
Assert.Equal(expectedArgs1[i].ToString(), getBuild1.Args[i].ToString());
}
Assert.Equal("JSON.GET", getBuild1.Command);
- for(int i = 0; i < expectedArgs2.Length; i++)
+ for (int i = 0; i < expectedArgs2.Length; i++)
{
Assert.Equal(expectedArgs2[i].ToString(), getBuild2.Args[i].ToString());
}