Skip to content

Commit

Permalink
Merge pull request #41 from adam-mccoy/master
Browse files Browse the repository at this point in the history
Fix non-compliant RFC 6902
  • Loading branch information
wbish authored Sep 11, 2020
2 parents d1b5a6f + 831ec94 commit a0508be
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 38 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,5 @@ _Pvt_Extensions

# FAKE - F# Make
.fake/

.idea/
95 changes: 62 additions & 33 deletions Src/JsonDiffPatchDotNet.UnitTests/JsonDeltaFormatterUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,57 +7,63 @@ namespace JsonDiffPatchDotNet.UnitTests
[TestFixture]
public class JsonDeltaFormatterUnitTests
{
private static readonly JsonDiffPatch Differ = new JsonDiffPatch();
private static readonly JsonDeltaFormatter Formatter = new JsonDeltaFormatter();

[Test]
public void Format_EmptyDiffIsEmpty_Success()
{
var left = JObject.Parse(@"{}");
var right = JObject.Parse(@"{}");
var patch = Differ.Diff(left, right);
var operations = Formatter.Format(patch);

Assert.AreEqual(0, operations.Count);
}

[Test]
public void Format_SupportsRemove_Success()
{
var jdp = new JsonDiffPatch();
var formatter = new JsonDeltaFormatter();
var left = JObject.Parse(@"{ ""p"" : true }");
var right = JObject.Parse(@"{ }");
var patch = jdp.Diff(left, right);
var operations = formatter.Format(patch);
var left = JObject.Parse(@"{ ""a"": ""a"", ""b"": ""b"", ""c"" : ""c"" }");
var right = JObject.Parse(@"{ ""a"": ""a"", ""b"": ""b"" }");
var patch = Differ.Diff(left, right);
var operations = Formatter.Format(patch);

Assert.AreEqual(1, operations.Count);
AssertOperation(operations[0], OperationTypes.Remove, "/p");
AssertOperation(operations[0], OperationTypes.Remove, "/c");
}

[Test]
public void Format_SupportsAdd_Success()
{
var jdp = new JsonDiffPatch();
var formatter = new JsonDeltaFormatter();
var left = JObject.Parse(@"{ }");
var right = JObject.Parse(@"{ ""p"" : true }");
var patch = jdp.Diff(left, right);
var operations = formatter.Format(patch);
var left = JObject.Parse(@"{ ""a"": ""a"", ""b"": ""b"" }");
var right = JObject.Parse(@"{ ""a"": ""a"", ""b"": ""b"", ""c"" : ""c"" }");
var patch = Differ.Diff(left, right);
var operations = Formatter.Format(patch);

Assert.AreEqual(1, operations.Count);
AssertOperation(operations[0], OperationTypes.Add, "/p", new JValue(true));
AssertOperation(operations[0], OperationTypes.Add, "/c", new JValue("c"));
}

[Test]
public void Format_SupportsReplace_Success()
{
var jdp = new JsonDiffPatch();
var formatter = new JsonDeltaFormatter();
var left = JObject.Parse(@"{ ""p"" : false }");
var right = JObject.Parse(@"{ ""p"" : true }");
var patch = jdp.Diff(left, right);
var operations = formatter.Format(patch);
var left = JObject.Parse(@"{ ""a"": ""a"", ""b"": ""b"" }");
var right = JObject.Parse(@"{ ""a"": ""a"", ""b"": ""c"" }");
var patch = Differ.Diff(left, right);
var operations = Formatter.Format(patch);

Assert.AreEqual(1, operations.Count);
AssertOperation(operations[0], OperationTypes.Replace, "/p", new JValue(true));
AssertOperation(operations[0], OperationTypes.Replace, "/b", new JValue("c"));
}

[Test]
public void Format_SupportsArrayAdd_Success()
{
var jdp = new JsonDiffPatch();
var formatter = new JsonDeltaFormatter();
var left = JObject.Parse(@"{ ""items"" : [""car"", ""bus""] }");
var right = JObject.Parse(@"{ ""items"" : [""bike"", ""car"", ""bus""] }");
var patch = jdp.Diff(left, right);
var operations = formatter.Format(patch);
var patch = Differ.Diff(left, right);
var operations = Formatter.Format(patch);

Assert.AreEqual(1, operations.Count);
AssertOperation(operations[0], OperationTypes.Add, "/items/0", JValue.CreateString("bike"));
Expand All @@ -66,12 +72,10 @@ public void Format_SupportsArrayAdd_Success()
[Test]
public void Format_SupportsArrayRemove_Success()
{
var jdp = new JsonDiffPatch();
var formatter = new JsonDeltaFormatter();
var left = JObject.Parse(@"{ ""items"" : [""bike"", ""car"", ""bus""] }");
var right = JObject.Parse(@"{ ""items"" : [""car"", ""bus""] }");
var patch = jdp.Diff(left, right);
var operations = formatter.Format(patch);
var patch = Differ.Diff(left, right);
var operations = Formatter.Format(patch);

Assert.AreEqual(1, operations.Count);
AssertOperation(operations[0], OperationTypes.Remove, "/items/0");
Expand All @@ -80,18 +84,43 @@ public void Format_SupportsArrayRemove_Success()
[Test]
public void Format_SupportsArrayMove_Success()
{
var jdp = new JsonDiffPatch();
var formatter = new JsonDeltaFormatter();
var left = JObject.Parse(@"{ ""items"" : [""bike"", ""car"", ""bus""] }");
var right = JObject.Parse(@"{ ""items"" : [""bike"", ""bus"", ""car""] }");
var patch = jdp.Diff(left, right);
var operations = formatter.Format(patch);
var patch = Differ.Diff(left, right);
var operations = Formatter.Format(patch);

Assert.AreEqual(2, operations.Count);
AssertOperation(operations[0], OperationTypes.Remove, "/items/2");
AssertOperation(operations[1], OperationTypes.Add, "/items/1", JValue.CreateString("bus"));
}

[Test]
public void Format_ArrayAddsInAscOrder_Success()
{
var left = JArray.Parse(@"[]");
var right = JArray.Parse(@"[1, 2, 3]");
var patch = Differ.Diff(left, right);
var operations = Formatter.Format(patch);

Assert.AreEqual(3, operations.Count);
AssertOperation(operations[0], OperationTypes.Add, "/0", new JValue(1));
AssertOperation(operations[1], OperationTypes.Add, "/1", new JValue(2));
AssertOperation(operations[2], OperationTypes.Add, "/2", new JValue(3));
}

[Test]
public void Format_ArrayRemoveInDescOrder_Success()
{
var left = JArray.Parse("[1, 2, 3]");
var right = JArray.Parse("[1]");
var patch = Differ.Diff(left, right);
var operations = Formatter.Format(patch);

Assert.AreEqual(2, operations.Count);
AssertOperation(operations[0], OperationTypes.Remove, "/2");
AssertOperation(operations[1], OperationTypes.Remove, "/1");
}

private void AssertOperation(Operation operation, string expectedOp, string expectedPath, JValue expectedValue = null)
{
Assert.AreEqual(expectedOp, operation.Op);
Expand Down
5 changes: 1 addition & 4 deletions Src/JsonDiffPatchDotNet/Formatters/ArrayKeyComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ internal class ArrayKeyComparer : IComparer<string>
{
public int Compare(string x, string y)
{
// This purposefully REVERSED from benjamine/jsondiffpatch,
// In order to match logic found in JsonDiffPatch.ArrayPatch,
// which applies operations in reverse order to avoid shifting floor
return ArrayKeyToSortNumber(y) - ArrayKeyToSortNumber(x);
return ArrayKeyToSortNumber(x) - ArrayKeyToSortNumber(y);
}

private static int ArrayKeyToSortNumber(string key)
Expand Down
2 changes: 1 addition & 1 deletion Src/JsonDiffPatchDotNet/Formatters/BaseDeltaFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public abstract class BaseDeltaFormatter<TContext, TResult>

private static readonly IComparer<string> s_arrayKeyComparer = new ArrayKeyComparer();

public TResult Format(JToken delta)
public virtual TResult Format(JToken delta)
{
var context = new TContext();
Recurse(context, delta, left: null, key: null, leftKey: null, movedFrom: null, isLast: false);
Expand Down
46 changes: 46 additions & 0 deletions Src/JsonDiffPatchDotNet/Formatters/JsonPatch/JsonDeltaFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;

namespace JsonDiffPatchDotNet.Formatters.JsonPatch
Expand All @@ -8,6 +9,12 @@ public class JsonDeltaFormatter : BaseDeltaFormatter<JsonFormatContext, IList<Op
{
protected override bool IncludeMoveDestinations => true;

public override IList<Operation> Format(JToken delta)
{
var result = base.Format(delta);
return ReorderOps(result);
}

protected override void Format(DeltaType type, JsonFormatContext context, JToken delta, JToken leftValue, string key, string leftKey, MoveDestination movedFrom)
{
switch (type)
Expand Down Expand Up @@ -81,5 +88,44 @@ private void FormatMoved(JsonFormatContext context, JToken delta)
{
context.PushMoveOp(delta[1].ToString());
}

private IList<Operation> ReorderOps(IList<Operation> result)
{
var removeOpsOtherOps = PartitionRemoveOps(result);
var removeOps = removeOpsOtherOps[0];
var otherOps = removeOpsOtherOps[1];
Array.Sort(removeOps, new RemoveOperationComparer());
return removeOps.Concat(otherOps).ToList();
}

private IList<Operation[]> PartitionRemoveOps(IList<Operation> result)
{
var left = new List<Operation>();
var right = new List<Operation>();

foreach (var op in result)
(op.Op.Equals("remove", StringComparison.Ordinal) ? left : right).Add(op);

return new List<Operation[]> {left.ToArray(), right.ToArray()};
}

private class RemoveOperationComparer : IComparer<Operation>
{
public int Compare(Operation a, Operation b)
{
if (a == null) throw new ArgumentNullException(nameof(a));
if (b == null) throw new ArgumentNullException(nameof(b));

var splitA = a.Path.Split('/');
var splitB = b.Path.Split('/');

return splitA.Length != splitB.Length
? splitA.Length - splitB.Length
: CompareByIndexDesc(splitA.Last(), splitB.Last());
}

private static int CompareByIndexDesc(string indexA, string indexB)
=> int.TryParse(indexA, out var a) && int.TryParse(indexB, out var b) ? b - a : 0;
}
}
}

0 comments on commit a0508be

Please sign in to comment.