/// <summary> /// 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. /// </summary> /// <param name="left">The base JSON object</param> /// <param name="right">The JSON object to compare against the base</param> /// <returns>JSON Patch Document</returns> public JToken Diff(JToken left, JToken right) { var objectHash = this._options.ObjectHash; var itemMatch = new DefaultItemMatch(objectHash); 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) { 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 <Patch> patches = dmp.patch_make(left.ToObject <string>(), right.ToObject <string>()); return(patches.Any() ? new JArray(dmp.patch_toText(patches), 0, (int)DiffOperation.TextDiff) : null); } if (!itemMatch.Match(left, right)) { return(new JArray(left, right)); } return(null); }
private JObject ArrayDiff(JArray left, JArray right) { var objectHash = this._options.ObjectHash; var itemMatch = new DefaultItemMatch(objectHash); var result = JObject.Parse(@"{ ""_t"": ""a"" }"); int commonHead = 0; int commonTail = 0; if (itemMatch.Match(left, right)) { return(null); } var childContext = new List <JToken>(); // Find common head while (commonHead < left.Count && commonHead < right.Count && itemMatch.Match(left[commonHead], right[commonHead])) { var index = commonHead; var child = Diff(left[index], right[index]); if (child != null) { result[$"{index}"] = child; } commonHead++; } // Find common tail while (commonTail + commonHead < left.Count && commonTail + commonHead < right.Count && itemMatch.Match(left[left.Count - 1 - commonTail], right[right.Count - 1 - commonTail])) { var index1 = left.Count - 1 - commonTail; var index2 = right.Count - 1 - commonTail; var child = Diff(left[index1], right[index2]); if (child != null) { result[$"{index2}"] = child; } 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) { if (result.ContainsKey(index.ToString())) { result.Remove(index.ToString()); } result[$"_{index}"] = new JArray(left[index], 0, (int)DiffOperation.Deleted); } return(result); } // Complex Diff, find the LCS (Longest Common Subsequence) List <JToken> trimmedLeft = left.ToList().GetRange(commonHead, left.Count - commonTail - commonHead); List <JToken> trimmedRight = right.ToList().GetRange(commonHead, right.Count - commonTail - commonHead); Lcs lcs = Lcs.Get(trimmedLeft, trimmedRight, itemMatch); for (int index = commonHead; index < left.Count - commonTail; ++index) { if (lcs.Indices1.IndexOf(index - commonHead) < 0) { // Removed if (result.ContainsKey(index.ToString())) { result.Remove(index.ToString()); } 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); }