Skip to content

Commit c98bb3f

Browse files
committed
Merge remote-tracking branch 'khiemnd777/master'
Conflicts: Src/JsonDiffPatchDotNet/JsonDiffPatch.cs Src/JsonDiffPatchDotNet/Options.cs
2 parents d0abb0e + 5e28084 commit c98bb3f

File tree

7 files changed

+262
-141
lines changed

7 files changed

+262
-141
lines changed

Src/JsonDiffPatchDotNet.UnitTests/DiffUnitTests.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,9 @@ public void Diff_EfficientArrayDiffDifferentHeadTailAdded_ValidDiff()
225225
[Test]
226226
public void Diff_EfficientArrayDiffSameLengthNested_ValidDiff()
227227
{
228-
var jdp = new JsonDiffPatch(new Options { ArrayDiff = ArrayDiffMode.Efficient });
229-
var left = JToken.Parse(@"[1,2,{""p"":false},4]");
230-
var right = JToken.Parse(@"[1,2,{""p"":true},4]");
228+
var jdp = new JsonDiffPatch(new Options { ArrayDiff = ArrayDiffMode.Efficient, ObjectHash = (jObj) => jObj["Id"].Value<string>() });
229+
var left = JToken.Parse(@"[1,2,{""Id"" : ""F12B21EF-F57D-4958-ADDC-A3F52EC25EC8"", ""p"":false},4]");
230+
var right = JToken.Parse(@"[1,2,{""Id"" : ""F12B21EF-F57D-4958-ADDC-A3F52EC25EC8"", ""p"":true},4]");
231231

232232
JObject diff = jdp.Diff(left, right) as JObject;
233233

@@ -236,7 +236,21 @@ public void Diff_EfficientArrayDiffSameLengthNested_ValidDiff()
236236
Assert.IsNotNull(diff["2"]);
237237
}
238238

239-
[Test]
239+
[Test]
240+
public void Diff_EfficientArrayDiffWithComplexObject_ValidDiff()
241+
{
242+
var jdp = new JsonDiffPatch(new Options { ArrayDiff = ArrayDiffMode.Efficient, ObjectHash = (jObj) => jObj["Id"].Value<string>() });
243+
//var jdp = new JsonDiffPatch(new Options { ArrayDiff = ArrayDiffMode.Efficient });
244+
var left = JToken.Parse(@"[{""Id"" : ""F12B21EF-F57D-4958-ADDC-A3F52EC25EC8"", ""p"":false}, {""Id"" : ""F12B21EF-F57D-4958-ADDC-A3F52EC25EC9"", ""p"":true}]");
245+
var right = JToken.Parse(@"[{""Id"" : ""F12B21EF-F57D-4958-ADDC-A3F52EC25EC8"", ""p"":true}, {""Id"" : ""F12B21EF-F57D-4958-ADDC-A3F52EC25EC10"", ""p"":false}]");
246+
247+
JObject diff = jdp.Diff(left, right) as JObject;
248+
249+
Assert.IsNotNull(diff);
250+
Assert.AreEqual(4, diff.Properties().Count());
251+
}
252+
253+
[Test]
240254
public void Diff_EfficientArrayDiffSameWithObject_NoDiff()
241255
{
242256
var jdp = new JsonDiffPatch(new Options { ArrayDiff = ArrayDiffMode.Efficient });

Src/JsonDiffPatchDotNet/ArrayDiffMode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ public enum ArrayDiffMode
1616
/// the entire left and entire right arrays are added to the patch document as a simple
1717
/// JSON token replace. If they are the same, then token is skipped in the patch document.
1818
/// </summary>
19-
Simple,
19+
Simple
2020
}
2121
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Newtonsoft.Json.Linq;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace JsonDiffPatchDotNet
9+
{
10+
public class DefaultItemMatch : ItemMatch
11+
{
12+
public DefaultItemMatch(Func<JToken, object> objectHash):base(objectHash)
13+
{
14+
15+
}
16+
}
17+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using Newtonsoft.Json.Linq;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace JsonDiffPatchDotNet
9+
{
10+
public abstract class ItemMatch
11+
{
12+
internal Func<JToken, object> ObjectHash;
13+
14+
protected ItemMatch()
15+
{
16+
17+
}
18+
19+
protected ItemMatch(Func<JToken, object> objectHash)
20+
{
21+
ObjectHash = objectHash;
22+
}
23+
24+
public virtual bool Match(JToken object1, JToken object2)
25+
{
26+
return Match(object1, object2, ObjectHash);
27+
}
28+
29+
public virtual bool Match(JToken object1, JToken object2, Func<JToken, object> objectHash)
30+
{
31+
if(objectHash == null || object1.Type != JTokenType.Object)
32+
{
33+
return JToken.DeepEquals(object1, object2);
34+
}
35+
36+
var hash1 = objectHash.Invoke(object1);
37+
if(hash1 == null)
38+
{
39+
return false;
40+
}
41+
var hash2 = objectHash.Invoke(object2);
42+
if(hash2 == null)
43+
{
44+
return false;
45+
}
46+
return hash1.Equals(hash2);
47+
}
48+
}
49+
}

Src/JsonDiffPatchDotNet/JsonDiffPatch.cs

Lines changed: 116 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ public JsonDiffPatch(Options options)
3838
/// <returns>JSON Patch Document</returns>
3939
public JToken Diff(JToken left, JToken right)
4040
{
41+
42+
var objectHash = this._options.ObjectHash;
43+
var itemMatch = new DefaultItemMatch(objectHash);
44+
45+
4146
if (left == null)
4247
left = new JValue("");
4348
if (right == null)
@@ -67,7 +72,7 @@ public JToken Diff(JToken left, JToken right)
6772
: null;
6873
}
6974

70-
if (!JToken.DeepEquals(left, right))
75+
if (!itemMatch.Match(left, right))
7176
{
7277
return new JArray(left, right);
7378
}
@@ -371,91 +376,116 @@ private JObject ObjectDiff(JObject left, JObject right)
371376
}
372377

373378
private JObject ArrayDiff(JArray left, JArray right)
374-
{
375-
var result = JObject.Parse(@"{ ""_t"": ""a"" }");
376-
377-
int commonHead = 0;
378-
int commonTail = 0;
379-
380-
if (JToken.DeepEquals(left, right))
381-
return null;
382-
383-
// Find common head
384-
while (commonHead < left.Count
385-
&& commonHead < right.Count
386-
&& JToken.DeepEquals(left[commonHead], right[commonHead]))
387-
{
388-
commonHead++;
389-
}
390-
391-
// Find common tail
392-
while (commonTail + commonHead < left.Count
393-
&& commonTail + commonHead < right.Count
394-
&& JToken.DeepEquals(left[left.Count - 1 - commonTail], right[right.Count - 1 - commonTail]))
395-
{
396-
commonTail++;
397-
}
398-
399-
if (commonHead + commonTail == left.Count)
400-
{
401-
// Trivial case, a block (1 or more consecutive items) was added
402-
for (int index = commonHead; index < right.Count - commonTail; ++index)
403-
{
404-
result[$"{index}"] = new JArray(right[index]);
405-
}
406-
407-
return result;
408-
}
409-
if (commonHead + commonTail == right.Count)
410-
{
411-
// Trivial case, a block (1 or more consecutive items) was removed
412-
for (int index = commonHead; index < left.Count - commonTail; ++index)
413-
{
414-
result[$"_{index}"] = new JArray(left[index], 0, (int)DiffOperation.Deleted);
415-
}
416-
417-
return result;
418-
}
419-
420-
// Complex Diff, find the LCS (Longest Common Subsequence)
421-
List<JToken> trimmedLeft = left.ToList().GetRange(commonHead, left.Count - commonTail - commonHead);
422-
List<JToken> trimmedRight = right.ToList().GetRange(commonHead, right.Count - commonTail - commonHead);
423-
Lcs lcs = Lcs.Get(trimmedLeft, trimmedRight);
424-
425-
for (int index = commonHead; index < left.Count - commonTail; ++index)
426-
{
427-
if (lcs.Indices1.IndexOf(index - commonHead) < 0)
428-
{
429-
// Removed
430-
result[$"_{index}"] = new JArray(left[index], 0, (int)DiffOperation.Deleted);
431-
}
432-
}
433-
434-
for (int index = commonHead; index < right.Count - commonTail; index++)
435-
{
436-
int indexRight = lcs.Indices2.IndexOf(index - commonHead);
437-
438-
if (indexRight < 0)
439-
{
440-
// Added
441-
result[$"{index}"] = new JArray(right[index]);
442-
}
443-
else
444-
{
445-
int li = lcs.Indices1[indexRight] + commonHead;
446-
int ri = lcs.Indices2[indexRight] + commonHead;
447-
448-
JToken diff = Diff(left[li], right[ri]);
449-
450-
if (diff != null)
451-
{
452-
result[$"{index}"] = diff;
453-
}
454-
}
455-
}
456-
457-
return result;
458-
}
379+
{
380+
var objectHash = this._options.ObjectHash;
381+
var itemMatch = new DefaultItemMatch(objectHash);
382+
var result = JObject.Parse(@"{ ""_t"": ""a"" }");
383+
384+
int commonHead = 0;
385+
int commonTail = 0;
386+
387+
if (itemMatch.Match(left, right))
388+
return null;
389+
390+
var childContext = new List<JToken>();
391+
392+
// Find common head
393+
while (commonHead < left.Count
394+
&& commonHead < right.Count
395+
&& itemMatch.Match(left[commonHead], right[commonHead]))
396+
{
397+
var index = commonHead;
398+
var child = Diff(left[index], right[index]);
399+
if(child != null)
400+
{
401+
result[$"{index}"] = child;
402+
}
403+
commonHead++;
404+
}
405+
406+
// Find common tail
407+
while (commonTail + commonHead < left.Count
408+
&& commonTail + commonHead < right.Count
409+
&& itemMatch.Match(left[left.Count - 1 - commonTail], right[right.Count - 1 - commonTail]))
410+
{
411+
var index1 = left.Count - 1 - commonTail;
412+
var index2 = right.Count - 1 - commonTail;
413+
var child = Diff(left[index1], right[index2]);
414+
if(child != null)
415+
{
416+
result[$"{index2}"] = child;
417+
}
418+
commonTail++;
419+
}
420+
421+
if (commonHead + commonTail == left.Count)
422+
{
423+
// Trivial case, a block (1 or more consecutive items) was added
424+
for (int index = commonHead; index < right.Count - commonTail; ++index)
425+
{
426+
result[$"{index}"] = new JArray(right[index]);
427+
}
428+
429+
return result;
430+
}
431+
if (commonHead + commonTail == right.Count)
432+
{
433+
// Trivial case, a block (1 or more consecutive items) was removed
434+
for (int index = commonHead; index < left.Count - commonTail; ++index)
435+
{
436+
if (result.ContainsKey(index.ToString()))
437+
{
438+
result.Remove(index.ToString());
439+
}
440+
result[$"_{index}"] = new JArray(left[index], 0, (int)DiffOperation.Deleted);
441+
}
442+
443+
return result;
444+
}
445+
446+
// Complex Diff, find the LCS (Longest Common Subsequence)
447+
List<JToken> trimmedLeft = left.ToList().GetRange(commonHead, left.Count - commonTail - commonHead);
448+
List<JToken> trimmedRight = right.ToList().GetRange(commonHead, right.Count - commonTail - commonHead);
449+
Lcs lcs = Lcs.Get(trimmedLeft, trimmedRight, itemMatch);
450+
451+
for (int index = commonHead; index < left.Count - commonTail; ++index)
452+
{
453+
if (lcs.Indices1.IndexOf(index - commonHead) < 0)
454+
{
455+
// Removed
456+
if (result.ContainsKey(index.ToString()))
457+
{
458+
result.Remove(index.ToString());
459+
}
460+
result[$"_{index}"] = new JArray(left[index], 0, (int)DiffOperation.Deleted);
461+
}
462+
}
463+
464+
for (int index = commonHead; index < right.Count - commonTail; index++)
465+
{
466+
int indexRight = lcs.Indices2.IndexOf(index - commonHead);
467+
468+
if (indexRight < 0)
469+
{
470+
// Added
471+
result[$"{index}"] = new JArray(right[index]);
472+
}
473+
else
474+
{
475+
int li = lcs.Indices1[indexRight] + commonHead;
476+
int ri = lcs.Indices2[indexRight] + commonHead;
477+
478+
JToken diff = Diff(left[li], right[ri]);
479+
480+
if (diff != null)
481+
{
482+
result[$"{index}"] = diff;
483+
}
484+
}
485+
}
486+
487+
return result;
488+
}
459489

460490
private JObject ObjectPatch(JObject obj, JObject patch)
461491
{

0 commit comments

Comments
 (0)