diff --git a/Src/JsonDiffPatchDotNet/JsonDiffPatch.cs b/Src/JsonDiffPatchDotNet/JsonDiffPatch.cs
index 19d0dda..645d496 100644
--- a/Src/JsonDiffPatchDotNet/JsonDiffPatch.cs
+++ b/Src/JsonDiffPatchDotNet/JsonDiffPatch.cs
@@ -1,672 +1,672 @@
-using System;
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Globalization;
-using System.IO;
-using System.Linq;
-using DiffMatchPatch;
-using Newtonsoft.Json.Linq;
-
-namespace JsonDiffPatchDotNet
-{
- public class JsonDiffPatch
- {
- private readonly Options _options;
-
- public JsonDiffPatch()
- : this(new Options())
- {
- }
-
- public JsonDiffPatch(Options options)
- {
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
-
- _options = options;
- }
-
- ///
- /// Diff two JSON objects.
- ///
- /// The output is a JObject that contains enough information to represent the
- /// delta between the two objects and to be able perform patch and reverse operations.
- ///
- /// The base JSON object
- /// The JSON object to compare against the base
- /// JSON Patch Document
- public JToken Diff(JToken left, JToken right)
- {
- if (left == null)
- left = new JValue("");
- if (right == null)
- right = new JValue("");
-
- if (left.Type == JTokenType.Object && right.Type == JTokenType.Object)
+using System.IO;
+using System.Linq;
+using DiffMatchPatch;
+using Newtonsoft.Json.Linq;
+
+namespace JsonDiffPatchDotNet
+{
+ public class JsonDiffPatch
+ {
+ private readonly Options _options;
+
+ public JsonDiffPatch()
+ : this(new Options())
+ {
+ }
+
+ public JsonDiffPatch(Options options)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ _options = options;
+ }
+
+ ///
+ /// Diff two JSON objects.
+ ///
+ /// The output is a JObject that contains enough information to represent the
+ /// delta between the two objects and to be able perform patch and reverse operations.
+ ///
+ /// The base JSON object
+ /// The JSON object to compare against the base
+ /// JSON Patch Document
+ public JToken Diff(JToken left, JToken right)
+ {
+ if (left == null)
+ left = new JValue("");
+ if (right == null)
+ right = new JValue("");
+
+ if (left.Type == JTokenType.Object && right.Type == JTokenType.Object)
{
return ObjectDiff((JObject)left, (JObject)right);
- }
-
- if (_options.ArrayDiff == ArrayDiffMode.Efficient
- && left.Type == JTokenType.Array
- && right.Type == JTokenType.Array)
+ }
+
+ if (_options.ArrayDiff == ArrayDiffMode.Efficient
+ && left.Type == JTokenType.Array
+ && right.Type == JTokenType.Array)
{
return ArrayDiff((JArray)left, (JArray)right);
- }
-
- if (_options.TextDiff == TextDiffMode.Efficient
- && left.Type == JTokenType.String
- && right.Type == JTokenType.String
- && (left.ToString().Length > _options.MinEfficientTextDiffLength || right.ToString().Length > _options.MinEfficientTextDiffLength))
- {
- var dmp = new diff_match_patch();
- List patches = dmp.patch_make(left.ToObject(), right.ToObject());
- return patches.Any()
- ? new JArray(dmp.patch_toText(patches), 0, (int)DiffOperation.TextDiff)
- : null;
- }
-
+ }
+
+ if (_options.TextDiff == TextDiffMode.Efficient
+ && left.Type == JTokenType.String
+ && right.Type == JTokenType.String
+ && (left.ToString().Length > _options.MinEfficientTextDiffLength || right.ToString().Length > _options.MinEfficientTextDiffLength))
+ {
+ var dmp = new diff_match_patch();
+ List patches = dmp.patch_make(left.ToObject(), right.ToObject());
+ return patches.Any()
+ ? new JArray(dmp.patch_toText(patches), 0, (int)DiffOperation.TextDiff)
+ : null;
+ }
+
if (!JToken.DeepEquals(left, right))
{
return new JArray(left, right);
- }
-
- return null;
- }
-
- ///
- /// Patch a JSON object
- ///
- /// Unpatched JSON object
- /// JSON Patch Document
- /// Patched JSON object
- /// Thrown if the patch document is invalid
- public JToken Patch(JToken left, JToken patch)
- {
- if (patch == null)
- return left;
-
- if (patch.Type == JTokenType.Object)
- {
- var patchObj = (JObject)patch;
- JProperty arrayDiffCanary = patchObj.Property("_t");
-
- if (left != null
- && left.Type == JTokenType.Array
- && arrayDiffCanary != null
- && arrayDiffCanary.Value.Type == JTokenType.String
- && arrayDiffCanary.Value.ToObject() == "a")
- {
- return ArrayPatch((JArray)left, patchObj);
- }
-
- return ObjectPatch(left as JObject, patchObj);
- }
-
- if (patch.Type == JTokenType.Array)
- {
- var patchArray = (JArray)patch;
-
- if (patchArray.Count == 1) // Add
- {
- return patchArray[0];
- }
-
- if (patchArray.Count == 2) // Replace
- {
- return patchArray[1];
- }
-
- if (patchArray.Count == 3) // Delete, Move or TextDiff
- {
- if (patchArray[2].Type != JTokenType.Integer)
- throw new InvalidDataException("Invalid patch object");
-
- int op = patchArray[2].Value();
-
- if (op == 0)
- {
- return null;
- }
-
- if (op == 2)
- {
- if (left.Type != JTokenType.String)
- throw new InvalidDataException("Invalid patch object");
-
- var dmp = new diff_match_patch();
- List patches = dmp.patch_fromText(patchArray[0].ToObject());
-
- if (!patches.Any())
- throw new InvalidDataException("Invalid textline");
-
- object[] result = dmp.patch_apply(patches, left.Value());
- var patchResults = (bool[])result[1];
- if (patchResults.Any(x => !x))
- throw new InvalidDataException("Text patch failed");
-
- string right = (string)result[0];
- return right;
- }
-
- throw new InvalidDataException("Invalid patch object");
- }
-
- throw new InvalidDataException("Invalid patch object");
- }
-
- return null;
- }
-
- ///
- /// Unpatch a JSON object
- ///
- /// Patched JSON object
- /// JSON Patch Document
- /// Unpatched JSON object
- /// Thrown if the patch document is invalid
- public JToken Unpatch(JToken right, JToken patch)
- {
- if (patch == null)
- return right;
-
- if (patch.Type == JTokenType.Object)
- {
- var patchObj = (JObject)patch;
- JProperty arrayDiffCanary = patchObj.Property("_t");
-
- if (right != null
- && right.Type == JTokenType.Array
- && arrayDiffCanary != null
- && arrayDiffCanary.Value.Type == JTokenType.String
- && arrayDiffCanary.Value.ToObject() == "a")
- {
- return ArrayUnpatch((JArray)right, patchObj);
- }
-
- return ObjectUnpatch(right as JObject, patchObj);
- }
-
- if (patch.Type == JTokenType.Array)
- {
- var patchArray = (JArray)patch;
-
- if (patchArray.Count == 1) // Add (we need to remove the property)
- {
- return null;
- }
-
- if (patchArray.Count == 2) // Replace
- {
- return patchArray[0];
- }
-
- if (patchArray.Count == 3) // Delete, Move or TextDiff
- {
- if (patchArray[2].Type != JTokenType.Integer)
- throw new InvalidDataException("Invalid patch object");
-
- int op = patchArray[2].Value();
-
- if (op == 0)
- {
- return patchArray[0];
- }
- if (op == 2)
- {
- if (right.Type != JTokenType.String)
- throw new InvalidDataException("Invalid patch object");
-
- var dmp = new diff_match_patch();
- List patches = dmp.patch_fromText(patchArray[0].ToObject());
-
- if (!patches.Any())
- throw new InvalidDataException("Invalid textline");
-
- var unpatches = new List();
- for (int i = patches.Count - 1; i >= 0; --i)
- {
- Patch p = patches[i];
- var u = new Patch
- {
- length1 = p.length1,
- length2 = p.length2,
- start1 = p.start1,
- start2 = p.start2
- };
-
- foreach (Diff d in p.diffs)
- {
- if (d.operation == Operation.DELETE)
- {
- u.diffs.Add(new Diff(Operation.INSERT, d.text));
- }
- else if (d.operation == Operation.INSERT)
- {
- u.diffs.Add(new Diff(Operation.DELETE, d.text));
- }
- else
- {
- u.diffs.Add(d);
- }
- }
- unpatches.Add(u);
- }
-
- object[] result = dmp.patch_apply(unpatches, right.Value());
- var unpatchResults = (bool[])result[1];
- if (unpatchResults.Any(x => !x))
- throw new InvalidDataException("Text patch failed");
-
- string left = (string)result[0];
- return left;
- }
- throw new InvalidDataException("Invalid patch object");
- }
-
- throw new InvalidDataException("Invalid patch object");
- }
-
- return null;
- }
-
- #region String Overrides
-
- ///
- /// Diff two JSON objects.
- ///
- /// The output is a JObject that contains enough information to represent the
- /// delta between the two objects and to be able perform patch and reverse operations.
- ///
- /// The base JSON object
- /// The JSON object to compare against the base
- /// JSON Patch Document
- public string Diff(string left, string right)
- {
- JToken obj = Diff(JToken.Parse(left ?? ""), JToken.Parse(right ?? ""));
- return obj?.ToString();
- }
-
- ///
- /// Patch a JSON object
- ///
- /// Unpatched JSON object
- /// JSON Patch Document
- /// Patched JSON object
- /// Thrown if the patch document is invalid
- public string Patch(string left, string patch)
- {
- JToken patchedObj = Patch(JToken.Parse(left ?? ""), JToken.Parse(patch ?? ""));
- return patchedObj?.ToString();
- }
-
- ///
- /// Unpatch a JSON object
- ///
- /// Patched JSON object
- /// JSON Patch Document
- /// Unpatched JSON object
- /// Thrown if the patch document is invalid
- public string Unpatch(string right, string patch)
- {
- JToken unpatchedObj = Unpatch(JToken.Parse(right ?? ""), JToken.Parse(patch ?? ""));
- return unpatchedObj?.ToString();
- }
-
- #endregion
-
- private JObject ObjectDiff(JObject left, JObject right)
- {
- if (left == null)
- throw new ArgumentNullException(nameof(left));
- if (right == null)
- throw new ArgumentNullException(nameof(right));
-
- var diffPatch = new JObject();
-
- // Find properties modified or deleted
- foreach (var lp in left.Properties())
+ }
+
+ return null;
+ }
+
+ ///
+ /// Patch a JSON object
+ ///
+ /// Unpatched JSON object
+ /// JSON Patch Document
+ /// Patched JSON object
+ /// Thrown if the patch document is invalid
+ public JToken Patch(JToken left, JToken patch)
+ {
+ if (patch == null)
+ return left;
+
+ if (patch.Type == JTokenType.Object)
+ {
+ var patchObj = (JObject)patch;
+ JProperty arrayDiffCanary = patchObj.Property("_t");
+
+ if (left != null
+ && left.Type == JTokenType.Array
+ && arrayDiffCanary != null
+ && arrayDiffCanary.Value.Type == JTokenType.String
+ && arrayDiffCanary.Value.ToObject() == "a")
+ {
+ return ArrayPatch((JArray)left, patchObj);
+ }
+
+ return ObjectPatch(left as JObject, patchObj);
+ }
+
+ if (patch.Type == JTokenType.Array)
+ {
+ var patchArray = (JArray)patch;
+
+ if (patchArray.Count == 1) // Add
+ {
+ return patchArray[0];
+ }
+
+ if (patchArray.Count == 2) // Replace
+ {
+ return patchArray[1];
+ }
+
+ if (patchArray.Count == 3) // Delete, Move or TextDiff
+ {
+ if (patchArray[2].Type != JTokenType.Integer)
+ throw new InvalidDataException("Invalid patch object");
+
+ int op = patchArray[2].Value();
+
+ if (op == 0)
+ {
+ return null;
+ }
+
+ if (op == 2)
+ {
+ if (left.Type != JTokenType.String)
+ throw new InvalidDataException("Invalid patch object");
+
+ var dmp = new diff_match_patch();
+ List patches = dmp.patch_fromText(patchArray[0].ToObject());
+
+ if (!patches.Any())
+ throw new InvalidDataException("Invalid textline");
+
+ object[] result = dmp.patch_apply(patches, left.Value());
+ var patchResults = (bool[])result[1];
+ if (patchResults.Any(x => !x))
+ throw new InvalidDataException("Text patch failed");
+
+ string right = (string)result[0];
+ return right;
+ }
+
+ throw new InvalidDataException("Invalid patch object");
+ }
+
+ throw new InvalidDataException("Invalid patch object");
+ }
+
+ return null;
+ }
+
+ ///
+ /// Unpatch a JSON object
+ ///
+ /// Patched JSON object
+ /// JSON Patch Document
+ /// Unpatched JSON object
+ /// Thrown if the patch document is invalid
+ public JToken Unpatch(JToken right, JToken patch)
+ {
+ if (patch == null)
+ return right;
+
+ if (patch.Type == JTokenType.Object)
+ {
+ var patchObj = (JObject)patch;
+ JProperty arrayDiffCanary = patchObj.Property("_t");
+
+ if (right != null
+ && right.Type == JTokenType.Array
+ && arrayDiffCanary != null
+ && arrayDiffCanary.Value.Type == JTokenType.String
+ && arrayDiffCanary.Value.ToObject() == "a")
+ {
+ return ArrayUnpatch((JArray)right, patchObj);
+ }
+
+ return ObjectUnpatch(right as JObject, patchObj);
+ }
+
+ if (patch.Type == JTokenType.Array)
+ {
+ var patchArray = (JArray)patch;
+
+ if (patchArray.Count == 1) // Add (we need to remove the property)
+ {
+ return null;
+ }
+
+ if (patchArray.Count == 2) // Replace
+ {
+ return patchArray[0];
+ }
+
+ if (patchArray.Count == 3) // Delete, Move or TextDiff
+ {
+ if (patchArray[2].Type != JTokenType.Integer)
+ throw new InvalidDataException("Invalid patch object");
+
+ int op = patchArray[2].Value();
+
+ if (op == 0)
+ {
+ return patchArray[0];
+ }
+ if (op == 2)
+ {
+ if (right.Type != JTokenType.String)
+ throw new InvalidDataException("Invalid patch object");
+
+ var dmp = new diff_match_patch();
+ List patches = dmp.patch_fromText(patchArray[0].ToObject());
+
+ if (!patches.Any())
+ throw new InvalidDataException("Invalid textline");
+
+ var unpatches = new List();
+ for (int i = patches.Count - 1; i >= 0; --i)
+ {
+ Patch p = patches[i];
+ var u = new Patch
+ {
+ length1 = p.length1,
+ length2 = p.length2,
+ start1 = p.start1,
+ start2 = p.start2
+ };
+
+ foreach (Diff d in p.diffs)
+ {
+ if (d.operation == Operation.DELETE)
+ {
+ u.diffs.Add(new Diff(Operation.INSERT, d.text));
+ }
+ else if (d.operation == Operation.INSERT)
+ {
+ u.diffs.Add(new Diff(Operation.DELETE, d.text));
+ }
+ else
+ {
+ u.diffs.Add(d);
+ }
+ }
+ unpatches.Add(u);
+ }
+
+ object[] result = dmp.patch_apply(unpatches, right.Value());
+ var unpatchResults = (bool[])result[1];
+ if (unpatchResults.Any(x => !x))
+ throw new InvalidDataException("Text patch failed");
+
+ string left = (string)result[0];
+ return left;
+ }
+ throw new InvalidDataException("Invalid patch object");
+ }
+
+ throw new InvalidDataException("Invalid patch object");
+ }
+
+ return null;
+ }
+
+ #region String Overrides
+
+ ///
+ /// Diff two JSON objects.
+ ///
+ /// The output is a JObject that contains enough information to represent the
+ /// delta between the two objects and to be able perform patch and reverse operations.
+ ///
+ /// The base JSON object
+ /// The JSON object to compare against the base
+ /// JSON Patch Document
+ public string Diff(string left, string right)
+ {
+ JToken obj = Diff(JToken.Parse(left ?? ""), JToken.Parse(right ?? ""));
+ return obj?.ToString();
+ }
+
+ ///
+ /// Patch a JSON object
+ ///
+ /// Unpatched JSON object
+ /// JSON Patch Document
+ /// Patched JSON object
+ /// Thrown if the patch document is invalid
+ public string Patch(string left, string patch)
+ {
+ JToken patchedObj = Patch(JToken.Parse(left ?? ""), JToken.Parse(patch ?? ""));
+ return patchedObj?.ToString();
+ }
+
+ ///
+ /// Unpatch a JSON object
+ ///
+ /// Patched JSON object
+ /// JSON Patch Document
+ /// Unpatched JSON object
+ /// Thrown if the patch document is invalid
+ public string Unpatch(string right, string patch)
+ {
+ JToken unpatchedObj = Unpatch(JToken.Parse(right ?? ""), JToken.Parse(patch ?? ""));
+ return unpatchedObj?.ToString();
+ }
+
+ #endregion
+
+ private JObject ObjectDiff(JObject left, JObject right)
+ {
+ if (left == null)
+ throw new ArgumentNullException(nameof(left));
+ if (right == null)
+ throw new ArgumentNullException(nameof(right));
+
+ var diffPatch = new JObject();
+
+ // Find properties modified or deleted
+ foreach (var lp in left.Properties())
{
//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;
+ if (rp == null && (_options.DiffBehaviors & DiffBehavior.IgnoreMissingProperties) == DiffBehavior.IgnoreMissingProperties)
+ {
+ continue;
}
-
- if (rp == null)
- {
- diffPatch.Add(new JProperty(lp.Name, new JArray(lp.Value, 0, (int)DiffOperation.Deleted)));
- continue;
- }
-
- JToken d = Diff(lp.Value, rp.Value);
- if (d != null)
- {
- diffPatch.Add(new JProperty(lp.Name, d));
- }
- }
-
- // Find properties that were added
- foreach (var rp in right.Properties())
+
+ if (rp == null)
+ {
+ diffPatch.Add(new JProperty(lp.Name, new JArray(lp.Value, 0, (int)DiffOperation.Deleted)));
+ continue;
+ }
+
+ JToken d = Diff(lp.Value, rp.Value);
+ if (d != null)
+ {
+ diffPatch.Add(new JProperty(lp.Name, d));
+ }
+ }
+
+ // Find properties that were added
+ foreach (var rp in right.Properties())
{
- if (left.Property(rp.Name) != null || (_options.DiffBehaviors & DiffBehavior.IgnoreNewProperties) == DiffBehavior.IgnoreNewProperties)
- continue;
-
- diffPatch.Add(new JProperty(rp.Name, new JArray(rp.Value)));
- }
-
- if (diffPatch.Properties().Any())
- return diffPatch;
-
- return null;
- }
-
- private JObject ArrayDiff(JArray left, JArray right)
- {
- var result = JObject.Parse(@"{ ""_t"": ""a"" }");
-
- int commonHead = 0;
- int commonTail = 0;
-
- if (JToken.DeepEquals(left, right))
- return null;
-
- // Find common head
- while (commonHead < left.Count
- && commonHead < right.Count
- && JToken.DeepEquals(left[commonHead], right[commonHead]))
- {
- commonHead++;
- }
-
- // Find common tail
- while (commonTail + commonHead < left.Count
- && commonTail + commonHead < right.Count
- && JToken.DeepEquals(left[left.Count - 1 - commonTail], right[right.Count - 1 - commonTail]))
- {
- commonTail++;
- }
-
- if (commonHead + commonTail == left.Count)
- {
- // Trivial case, a block (1 or more consecutive items) was added
- for (int index = commonHead; index < right.Count - commonTail; ++index)
- {
- result[$"{index}"] = new JArray(right[index]);
- }
-
- return result;
- }
- if (commonHead + commonTail == right.Count)
- {
- // Trivial case, a block (1 or more consecutive items) was removed
- for (int index = commonHead; index < left.Count - commonTail; ++index)
- {
- result[$"_{index}"] = new JArray(left[index], 0, (int)DiffOperation.Deleted);
- }
-
- return result;
- }
-
- // Complex Diff, find the LCS (Longest Common Subsequence)
- List trimmedLeft = left.ToList().GetRange(commonHead, left.Count - commonTail - commonHead);
- List trimmedRight = right.ToList().GetRange(commonHead, right.Count - commonTail - commonHead);
- Lcs lcs = Lcs.Get(trimmedLeft, trimmedRight);
-
- for (int index = commonHead; index < left.Count - commonTail; ++index)
- {
- if (lcs.Indices1.IndexOf(index - commonHead) < 0)
- {
- // Removed
- result[$"_{index}"] = new JArray(left[index], 0, (int)DiffOperation.Deleted);
- }
- }
-
- for (int index = commonHead; index < right.Count - commonTail; index++)
- {
- int indexRight = lcs.Indices2.IndexOf(index - commonHead);
-
- if (indexRight < 0)
- {
- // Added
- result[$"{index}"] = new JArray(right[index]);
- }
- else
- {
- int li = lcs.Indices1[indexRight] + commonHead;
- int ri = lcs.Indices2[indexRight] + commonHead;
-
- JToken diff = Diff(left[li], right[ri]);
-
- if (diff != null)
- {
- result[$"{index}"] = diff;
- }
- }
- }
-
- return result;
- }
-
- private JObject ObjectPatch(JObject obj, JObject patch)
- {
- if (obj == null)
- obj = new JObject();
- if (patch == null)
- return obj;
-
- var target = (JObject)obj.DeepClone();
-
- foreach (var diff in patch.Properties())
- {
- JProperty property = target.Property(diff.Name);
- JToken patchValue = diff.Value;
-
- // We need to special case deletion when doing objects since a delete is a removal of a property
- // not a null assignment
- if (patchValue.Type == JTokenType.Array && ((JArray)patchValue).Count == 3 && patchValue[2].Value() == 0)
- {
- target.Remove(diff.Name);
- }
- else
- {
- if (property == null)
- {
- target.Add(new JProperty(diff.Name, Patch(null, patchValue)));
- }
- else
- {
- property.Value = Patch(property.Value, patchValue);
- }
- }
- }
-
- return target;
- }
-
- private JArray ArrayPatch(JArray left, JObject patch)
- {
- var toRemove = new List();
- var toInsert = new List();
- var toModify = new List();
-
- foreach (JProperty op in patch.Properties())
- {
- if (op.Name == "_t")
- continue;
-
- var value = op.Value as JArray;
-
- if (op.Name.StartsWith("_"))
- {
- // removed item from original array
- if (value != null && value.Count == 3 && (value[2].ToObject() == (int)DiffOperation.Deleted || value[2].ToObject() == (int)DiffOperation.ArrayMove))
- {
- toRemove.Add(new JProperty(op.Name.Substring(1), op.Value));
-
- if (value[2].ToObject() == (int)DiffOperation.ArrayMove)
- toInsert.Add(new JProperty(value[1].ToObject().ToString(), new JArray(left[int.Parse(op.Name.Substring(1))].DeepClone())));
- }
- else
- {
- throw new Exception($"Only removal or move can be applied at original array indices. Context: {value}");
- }
- }
- else
- {
- if (value != null && value.Count == 1)
- {
- toInsert.Add(op);
- }
- else
- {
- toModify.Add(op);
- }
- }
- }
-
-
- // remove items, in reverse order to avoid sawing our own floor
- toRemove.Sort((x, y) => int.Parse(x.Name).CompareTo(int.Parse(y.Name)));
- for (int i = toRemove.Count - 1; i >= 0; --i)
- {
- JProperty op = toRemove[i];
- left.RemoveAt(int.Parse(op.Name));
- }
-
- // insert items, in reverse order to avoid moving our own floor
- toInsert.Sort((x, y) => int.Parse(y.Name).CompareTo(int.Parse(x.Name)));
- for (int i = toInsert.Count - 1; i >= 0; --i)
- {
- JProperty op = toInsert[i];
- left.Insert(int.Parse(op.Name), ((JArray)op.Value)[0]);
- }
-
- foreach (var op in toModify)
- {
- JToken p = Patch(left[int.Parse(op.Name)], op.Value);
- left[int.Parse(op.Name)] = p;
- }
-
- return left;
- }
-
- private JObject ObjectUnpatch(JObject obj, JObject patch)
- {
- if (obj == null)
- obj = new JObject();
- if (patch == null)
- return obj;
-
- var target = (JObject)obj.DeepClone();
-
- foreach (var diff in patch.Properties())
- {
- JProperty property = target.Property(diff.Name);
- JToken patchValue = diff.Value;
-
- // We need to special case addition when doing objects since an undo add is a removal of a property
- // not a null assignment
- if (patchValue.Type == JTokenType.Array && ((JArray)patchValue).Count == 1)
- {
- target.Remove(property.Name);
- }
- else
- {
- if (property == null)
- {
- target.Add(new JProperty(diff.Name, Unpatch(null, patchValue)));
- }
- else
- {
- property.Value = Unpatch(property.Value, patchValue);
- }
- }
- }
-
- return target;
- }
-
- private JArray ArrayUnpatch(JArray right, JObject patch)
- {
- var toRemove = new List();
- var toInsert = new List();
- var toModify = new List();
-
- foreach (JProperty op in patch.Properties())
- {
- if (op.Name == "_t")
- continue;
-
- var value = op.Value as JArray;
-
- if (op.Name.StartsWith("_"))
- {
- // removed item from original array
- if (value != null && value.Count == 3 && (value[2].ToObject() == (int)DiffOperation.Deleted || value[2].ToObject() == (int)DiffOperation.ArrayMove))
- {
- var newOp = new JProperty(value[1].ToObject().ToString(), op.Value);
-
- if (value[2].ToObject() == (int)DiffOperation.ArrayMove)
- {
- toInsert.Add(new JProperty(op.Name.Substring(1), new JArray(right[value[1].ToObject()].DeepClone())));
- toRemove.Add(newOp);
- }
- else
- {
- toInsert.Add(new JProperty(op.Name.Substring(1), new JArray(value[0])));
- }
- }
- else
- {
- throw new Exception($"Only removal or move can be applied at original array indices. Context: {value}");
- }
- }
- else
- {
- if (value != null && value.Count == 1)
- {
- toRemove.Add(op);
- }
- else
- {
- toModify.Add(op);
- }
- }
- }
-
- // first modify entries
- foreach (var op in toModify)
- {
- JToken p = Unpatch(right[int.Parse(op.Name)], op.Value);
- right[int.Parse(op.Name)] = p;
- }
-
- // remove items, in reverse order to avoid sawing our own floor
- toRemove.Sort((x, y) => int.Parse(x.Name).CompareTo(int.Parse(y.Name)));
- for (int i = toRemove.Count - 1; i >= 0; --i)
- {
- JProperty op = toRemove[i];
- right.RemoveAt(int.Parse(op.Name));
- }
-
- // insert items, in reverse order to avoid moving our own floor
- toInsert.Sort((x, y) => int.Parse(x.Name).CompareTo(int.Parse(y.Name)));
- foreach (var op in toInsert)
- {
- right.Insert(int.Parse(op.Name), ((JArray)op.Value)[0]);
- }
-
- return right;
- }
- }
-}
+ if (left.Property(rp.Name) != null || (_options.DiffBehaviors & DiffBehavior.IgnoreNewProperties) == DiffBehavior.IgnoreNewProperties)
+ continue;
+
+ diffPatch.Add(new JProperty(rp.Name, new JArray(rp.Value)));
+ }
+
+ if (diffPatch.Properties().Any())
+ return diffPatch;
+
+ return null;
+ }
+
+ private JObject ArrayDiff(JArray left, JArray right)
+ {
+ var result = JObject.Parse(@"{ ""_t"": ""a"" }");
+
+ int commonHead = 0;
+ int commonTail = 0;
+
+ if (JToken.DeepEquals(left, right))
+ return null;
+
+ // Find common head
+ while (commonHead < left.Count
+ && commonHead < right.Count
+ && JToken.DeepEquals(left[commonHead], right[commonHead]))
+ {
+ commonHead++;
+ }
+
+ // Find common tail
+ while (commonTail + commonHead < left.Count
+ && commonTail + commonHead < right.Count
+ && JToken.DeepEquals(left[left.Count - 1 - commonTail], right[right.Count - 1 - commonTail]))
+ {
+ commonTail++;
+ }
+
+ if (commonHead + commonTail == left.Count)
+ {
+ // Trivial case, a block (1 or more consecutive items) was added
+ for (int index = commonHead; index < right.Count - commonTail; ++index)
+ {
+ result[$"{index}"] = new JArray(right[index]);
+ }
+
+ return result;
+ }
+ if (commonHead + commonTail == right.Count)
+ {
+ // Trivial case, a block (1 or more consecutive items) was removed
+ for (int index = commonHead; index < left.Count - commonTail; ++index)
+ {
+ result[$"_{index}"] = new JArray(left[index], 0, (int)DiffOperation.Deleted);
+ }
+
+ return result;
+ }
+
+ // Complex Diff, find the LCS (Longest Common Subsequence)
+ List trimmedLeft = left.ToList().GetRange(commonHead, left.Count - commonTail - commonHead);
+ List trimmedRight = right.ToList().GetRange(commonHead, right.Count - commonTail - commonHead);
+ Lcs lcs = Lcs.Get(trimmedLeft, trimmedRight);
+
+ for (int index = commonHead; index < left.Count - commonTail; ++index)
+ {
+ if (lcs.Indices1.IndexOf(index - commonHead) < 0)
+ {
+ // Removed
+ result[$"_{index}"] = new JArray(left[index], 0, (int)DiffOperation.Deleted);
+ }
+ }
+
+ for (int index = commonHead; index < right.Count - commonTail; index++)
+ {
+ int indexRight = lcs.Indices2.IndexOf(index - commonHead);
+
+ if (indexRight < 0)
+ {
+ // Added
+ result[$"{index}"] = new JArray(right[index]);
+ }
+ else
+ {
+ int li = lcs.Indices1[indexRight] + commonHead;
+ int ri = lcs.Indices2[indexRight] + commonHead;
+
+ JToken diff = Diff(left[li], right[ri]);
+
+ if (diff != null)
+ {
+ result[$"{index}"] = diff;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private JObject ObjectPatch(JObject obj, JObject patch)
+ {
+ if (obj == null)
+ obj = new JObject();
+ if (patch == null)
+ return obj;
+
+ var target = (JObject)obj.DeepClone();
+
+ foreach (var diff in patch.Properties())
+ {
+ JProperty property = target.Property(diff.Name);
+ JToken patchValue = diff.Value;
+
+ // We need to special case deletion when doing objects since a delete is a removal of a property
+ // not a null assignment
+ if (patchValue.Type == JTokenType.Array && ((JArray)patchValue).Count == 3 && patchValue[2].Value() == 0)
+ {
+ target.Remove(diff.Name);
+ }
+ else
+ {
+ if (property == null)
+ {
+ target.Add(new JProperty(diff.Name, Patch(null, patchValue)));
+ }
+ else
+ {
+ property.Value = Patch(property.Value, patchValue);
+ }
+ }
+ }
+
+ return target;
+ }
+
+ private JArray ArrayPatch(JArray left, JObject patch)
+ {
+ var toRemove = new List();
+ var toInsert = new List();
+ var toModify = new List();
+
+ foreach (JProperty op in patch.Properties())
+ {
+ if (op.Name == "_t")
+ continue;
+
+ var value = op.Value as JArray;
+
+ if (op.Name.StartsWith("_"))
+ {
+ // removed item from original array
+ if (value != null && value.Count == 3 && (value[2].ToObject() == (int)DiffOperation.Deleted || value[2].ToObject() == (int)DiffOperation.ArrayMove))
+ {
+ toRemove.Add(new JProperty(op.Name.Substring(1), op.Value));
+
+ if (value[2].ToObject() == (int)DiffOperation.ArrayMove)
+ toInsert.Add(new JProperty(value[1].ToObject().ToString(), new JArray(left[int.Parse(op.Name.Substring(1))].DeepClone())));
+ }
+ else
+ {
+ throw new Exception($"Only removal or move can be applied at original array indices. Context: {value}");
+ }
+ }
+ else
+ {
+ if (value != null && value.Count == 1)
+ {
+ toInsert.Add(op);
+ }
+ else
+ {
+ toModify.Add(op);
+ }
+ }
+ }
+
+
+ // remove items, in reverse order to avoid sawing our own floor
+ toRemove.Sort((x, y) => int.Parse(x.Name).CompareTo(int.Parse(y.Name)));
+ for (int i = toRemove.Count - 1; i >= 0; --i)
+ {
+ JProperty op = toRemove[i];
+ left.RemoveAt(int.Parse(op.Name));
+ }
+
+ // insert items, in reverse order to avoid moving our own floor
+ toInsert.Sort((x, y) => int.Parse(y.Name).CompareTo(int.Parse(x.Name)));
+ for (int i = toInsert.Count - 1; i >= 0; --i)
+ {
+ JProperty op = toInsert[i];
+ left.Insert(int.Parse(op.Name), ((JArray)op.Value)[0]);
+ }
+
+ foreach (var op in toModify)
+ {
+ JToken p = Patch(left[int.Parse(op.Name)], op.Value);
+ left[int.Parse(op.Name)] = p;
+ }
+
+ return left;
+ }
+
+ private JObject ObjectUnpatch(JObject obj, JObject patch)
+ {
+ if (obj == null)
+ obj = new JObject();
+ if (patch == null)
+ return obj;
+
+ var target = (JObject)obj.DeepClone();
+
+ foreach (var diff in patch.Properties())
+ {
+ JProperty property = target.Property(diff.Name);
+ JToken patchValue = diff.Value;
+
+ // We need to special case addition when doing objects since an undo add is a removal of a property
+ // not a null assignment
+ if (patchValue.Type == JTokenType.Array && ((JArray)patchValue).Count == 1)
+ {
+ target.Remove(property.Name);
+ }
+ else
+ {
+ if (property == null)
+ {
+ target.Add(new JProperty(diff.Name, Unpatch(null, patchValue)));
+ }
+ else
+ {
+ property.Value = Unpatch(property.Value, patchValue);
+ }
+ }
+ }
+
+ return target;
+ }
+
+ private JArray ArrayUnpatch(JArray right, JObject patch)
+ {
+ var toRemove = new List();
+ var toInsert = new List();
+ var toModify = new List();
+
+ foreach (JProperty op in patch.Properties())
+ {
+ if (op.Name == "_t")
+ continue;
+
+ var value = op.Value as JArray;
+
+ if (op.Name.StartsWith("_"))
+ {
+ // removed item from original array
+ if (value != null && value.Count == 3 && (value[2].ToObject() == (int)DiffOperation.Deleted || value[2].ToObject() == (int)DiffOperation.ArrayMove))
+ {
+ var newOp = new JProperty(value[1].ToObject().ToString(), op.Value);
+
+ if (value[2].ToObject() == (int)DiffOperation.ArrayMove)
+ {
+ toInsert.Add(new JProperty(op.Name.Substring(1), new JArray(right[value[1].ToObject()].DeepClone())));
+ toRemove.Add(newOp);
+ }
+ else
+ {
+ toInsert.Add(new JProperty(op.Name.Substring(1), new JArray(value[0])));
+ }
+ }
+ else
+ {
+ throw new Exception($"Only removal or move can be applied at original array indices. Context: {value}");
+ }
+ }
+ else
+ {
+ if (value != null && value.Count == 1)
+ {
+ toRemove.Add(op);
+ }
+ else
+ {
+ toModify.Add(op);
+ }
+ }
+ }
+
+ // first modify entries
+ foreach (var op in toModify)
+ {
+ JToken p = Unpatch(right[int.Parse(op.Name)], op.Value);
+ right[int.Parse(op.Name)] = p;
+ }
+
+ // remove items, in reverse order to avoid sawing our own floor
+ toRemove.Sort((x, y) => int.Parse(x.Name).CompareTo(int.Parse(y.Name)));
+ for (int i = toRemove.Count - 1; i >= 0; --i)
+ {
+ JProperty op = toRemove[i];
+ right.RemoveAt(int.Parse(op.Name));
+ }
+
+ // insert items, in reverse order to avoid moving our own floor
+ toInsert.Sort((x, y) => int.Parse(x.Name).CompareTo(int.Parse(y.Name)));
+ foreach (var op in toInsert)
+ {
+ right.Insert(int.Parse(op.Name), ((JArray)op.Value)[0]);
+ }
+
+ return right;
+ }
+ }
+}
diff --git a/Src/JsonDiffPatchDotNet/JsonDiffPatchDotNet.csproj b/Src/JsonDiffPatchDotNet/JsonDiffPatchDotNet.csproj
index b68d95b..f36aa10 100644
--- a/Src/JsonDiffPatchDotNet/JsonDiffPatchDotNet.csproj
+++ b/Src/JsonDiffPatchDotNet/JsonDiffPatchDotNet.csproj
@@ -5,8 +5,8 @@
latest
JsonDiffPatchDotNet
JsonDiffPatchDotNet
- 2.2.0.0
- 2.2.0.0
+ 2.3.0.0
+ 2.3.0.0
true
https://github.com/wbish/jsondiffpatch.net
git
@@ -22,7 +22,7 @@
JsonDiffPatchDotNet.snk
2.12
true
- 2.2.0.0
+ 2.3.0.0