Resolves #34. This change extends the capabilities of the Option class that is leveraged by JsonDiffPatch to apply diff patches. This change introduces an ExcludePaths property that allows you to specify the paths of the properties that you want the patch process to ignore. This is useful for preventing Id or other sensitive fields from being updated. This change also introduces a DiffBehaviors property that allows you to modify the behavior of the diff patch process. Specifying a behavior of IgnoreMissingProperties will prevent the patch process from deleting properties from the source object that aren't specified in the patch object, creating an avenue for partial updates to be applied. Specifying a behavior of IgnoreNewProperties will pevent properties that exist in the patch object but not in the source object from being applied to the source object. The implementation is applied exactly in accordance to the comment I made on issue #34 - https://github.com/wbish/jsondiffpatch.net/issues/34

pull/46/head
Ryan Obray 2020-10-25 04:33:33 -06:00
parent a0508be76a
commit c40cf5dcff
5 changed files with 129 additions and 19 deletions

View File

@ -1,4 +1,5 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
@ -296,6 +297,6 @@ namespace JsonDiffPatchDotNet.UnitTests
Assert.AreEqual(2, array.Count);
Assert.AreEqual(left, array[0]);
Assert.AreEqual(right, array[1]);
}
}
}
}

View File

@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
@ -246,6 +247,61 @@ namespace JsonDiffPatchDotNet.UnitTests
var patched = jdp.Patch(left, patch);
Assert.IsTrue(JToken.DeepEquals(right.ToString(), patched.ToString()));
}
}
[Test]
public void Diff_ExcludePaths_ValidPatch()
{
var jdp = new JsonDiffPatch(new Options { ExcludePaths = new List<string>() { "id", "nested.id" } });
var left = JObject.Parse(@"{ ""id"": ""pid"", ""p"": ""old"", ""nested"": { ""id"":""nid"", ""p"":""old"" } }");
var right = JObject.Parse(@"{ ""id"": ""pid2"", ""p"": ""new"", ""nested"": { ""id"":""nid2"", ""p"":""new"" } }");
var expected = JObject.Parse(@"{ ""id"": ""pid"", ""p"": ""new"", ""nested"": { ""id"":""nid"", ""p"":""new"" } }");
var patch = jdp.Diff(left, right);
var patched = jdp.Patch(left, patch) as JObject;
Assert.AreEqual(expected.ToString(), patched.ToString());
}
[Test]
public void Diff_Behaviors_IgnoreMissingProperties_ValidPatch()
{
var jdp = new JsonDiffPatch(new Options { DiffBehaviors = DiffBehavior.IgnoreMissingProperties });
var left = JObject.Parse(@"{ ""id"": ""pid"", ""p"": ""old"", ""nested"": { ""id"":""nid"", ""p"":""old"" } }");
var right = JObject.Parse(@"{ ""p"": ""new"", ""nested"": { ""p"":""new"" }, ""newP"": ""new"" }");
var expected = JObject.Parse(@"{ ""id"": ""pid"", ""p"": ""new"", ""nested"": { ""id"":""nid"", ""p"":""new"" }, ""newP"": ""new"" }");
var patch = jdp.Diff(left, right);
var patched = jdp.Patch(left, patch) as JObject;
Assert.AreEqual(expected.ToString(), patched.ToString());
}
[Test]
public void Diff_Behaviors_IgnoreNewProperties_ValidPatch()
{
var jdp = new JsonDiffPatch(new Options { DiffBehaviors = DiffBehavior.IgnoreNewProperties });
var left = JObject.Parse(@"{ ""id"": ""pid"", ""p"": ""old"", ""nested"": { ""id"":""nid"", ""p"":""old"" } }");
var right = JObject.Parse(@"{ ""id"": ""pid2"", ""p"": ""new"", ""nested"": { ""id"":""nid2"", ""p"":""new"" }, ""newP"": ""new"" }");
var expected = JObject.Parse(@"{ ""id"": ""pid2"", ""p"": ""new"", ""nested"": { ""id"":""nid2"", ""p"":""new"" } }");
var patch = jdp.Diff(left, right);
var patched = jdp.Patch(left, patch) as JObject;
Assert.AreEqual(expected.ToString(), patched.ToString());
}
[Test]
public void Diff_ExludeAndBehaviors_ExcludeIgnoreMissingIgnoreNew_ValidPatch()
{
var jdp = new JsonDiffPatch(new Options {
ExcludePaths = new List<string>() { "id", "nested.id" },
DiffBehaviors = DiffBehavior.IgnoreMissingProperties | DiffBehavior.IgnoreNewProperties
});
var left = JObject.Parse(@"{ ""id"": ""pid"", ""p"": ""old"", ""nested"": { ""id"":""nid"", ""p"":""old"" } }");
var right = JObject.Parse(@"{ ""id"": ""pid2"", ""nested"": { ""id"":""nid2"" }, ""newP"": ""new"" }");
var expected = JObject.Parse(@"{ ""id"": ""pid"", ""p"": ""old"", ""nested"": { ""id"":""nid"", ""p"":""old"" } }");
var patch = jdp.Diff(left, right);
var patched = jdp.Patch(left, patch) as JObject;
Assert.AreEqual(expected.ToString(), patched.ToString());
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace JsonDiffPatchDotNet
{
[Flags]
public enum DiffBehavior
{
/// <summary>
/// Default behavior
/// </summary>
None,
/// <summary>
/// If the patch document is missing properties that are in the source document, leave the existing properties in place instead of deleting them
/// </summary>
IgnoreMissingProperties,
/// <summary>
/// If the patch document contains properties that aren't defined in the source document, ignore them instead of adding them
/// </summary>
IgnoreNewProperties
}
}

View File

@ -1,5 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using DiffMatchPatch;
@ -43,15 +44,15 @@ namespace JsonDiffPatchDotNet
right = new JValue("");
if (left.Type == JTokenType.Object && right.Type == JTokenType.Object)
{
return ObjectDiff((JObject)left, (JObject)right);
{
return ObjectDiff((JObject)left, (JObject)right);
}
if (_options.ArrayDiff == ArrayDiffMode.Efficient
&& left.Type == JTokenType.Array
&& right.Type == JTokenType.Array)
{
return ArrayDiff((JArray)left, (JArray)right);
{
return ArrayDiff((JArray)left, (JArray)right);
}
if (_options.TextDiff == TextDiffMode.Efficient
@ -66,8 +67,10 @@ namespace JsonDiffPatchDotNet
: null;
}
if (!JToken.DeepEquals(left, right))
return new JArray(left, right);
if (!JToken.DeepEquals(left, right))
{
return new JArray(left, right);
}
return null;
}
@ -324,10 +327,21 @@ namespace JsonDiffPatchDotNet
// Find properties modified or deleted
foreach (var lp in left.Properties())
{
JProperty rp = right.Property(lp.Name);
{
//Skip property if in path exclustions
if (_options.ExcludePaths.Count > 0 && _options.ExcludePaths.Any(p => p.Equals(lp.Path, StringComparison.OrdinalIgnoreCase)))
{
continue;
}
JProperty rp = right.Property(lp.Name);
// Property deleted
if (rp == null && (_options.DiffBehaviors & DiffBehavior.IgnoreMissingProperties) == DiffBehavior.IgnoreMissingProperties)
{
continue;
}
// Property deleted
if (rp == null)
{
diffPatch.Add(new JProperty(lp.Name, new JArray(lp.Value, 0, (int)DiffOperation.Deleted)));
@ -343,8 +357,8 @@ namespace JsonDiffPatchDotNet
// Find properties that were added
foreach (var rp in right.Properties())
{
if (left.Property(rp.Name) != null)
{
if (left.Property(rp.Name) != null || (_options.DiffBehaviors & DiffBehavior.IgnoreNewProperties) == DiffBehavior.IgnoreNewProperties)
continue;
diffPatch.Add(new JProperty(rp.Name, new JArray(rp.Value)));

View File

@ -1,7 +1,11 @@
namespace JsonDiffPatchDotNet
using System;
using System.Collections;
using System.Collections.Generic;
namespace JsonDiffPatchDotNet
{
public sealed class Options
{
{
public Options()
{
ArrayDiff = ArrayDiffMode.Efficient;
@ -23,6 +27,16 @@
/// The minimum string length required to use Efficient text diff. If the minimum
/// length is not met, simple text diff will be used. The default length is 50 characters.
/// </summary>
public long MinEfficientTextDiffLength { get; set; }
public long MinEfficientTextDiffLength { get; set; }
/// <summary>
/// Specifies which paths to exclude from the diff patch set
/// </summary>
public List<string> ExcludePaths { get; set; } = new List<string>();
/// <summary>
/// Specifies behaviors to apply to the diff patch set
/// </summary>
public DiffBehavior DiffBehaviors { get; set; }
}
}