using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Globalization;
using Newtonsoft.Json.Linq;
using JsonDiffPatchDotNet;
public static void Main()
var left = new WellHierarchyView{
FieldName = "Field Khiem",
CountryName = "Republic of Vietnam",
var right = new WellHierarchyView{
FieldName = "Field Khiem",
AreaName = "North America",
var diffPatcher = new JsonDiffPatcher<WellHierarchyView>(left
, new WellHierarchyDataJsonDiffFormatter()
, new WellHierarchyDataJsonDiffAdapter());
var deltaDiff = diffPatcher.Diff();
Console.WriteLine(deltaDiff);
var formatDiff = diffPatcher.Format();
Console.WriteLine("Delta Diff: " + deltaDiff);
public class WellHierarchyView
public string WellName { get; set; }
public string FieldName { get; set; }
public string AreaName { get; set; }
public string CountryName { get; set; }
public string RegionName { get; set; }
public sealed class JsonDiffPatcher<T>
private readonly T _left;
private readonly T _right;
private readonly IJsonDiffAdapter<T> _adapter;
private readonly JsonDiffFormatter _formatter;
private JToken _adaptedLeft;
private JToken _adaptedRight;
private JToken _deltaDiff;
public JsonDiffPatcher(T left, T right, JsonDiffFormatter formatter, IJsonDiffAdapter<T> adapter)
var jdp = new JsonDiffPatch();
_adaptedLeft = _adapter.Define(_left);
_adaptedRight = _adapter.Define(_right);
_deltaDiff = jdp.Diff(_adaptedLeft, _adaptedRight);
_formatter.Format(_deltaDiff, _adaptedLeft);
return _formatter.Context;
public interface IJsonDiffAdapter<in T>
public class WellHierarchyDataJsonDiffAdapter : IJsonDiffAdapter<WellHierarchyView>
public JToken Define(WellHierarchyView obj)
if (obj == null) throw new NullReferenceException();
return JObject.FromObject(obj);
throw new Exception(e.Message, e);
public class WellHierarchyDataJsonDiffFormatter : JsonDiffFormatter
public WellHierarchyDataJsonDiffFormatter() : base()
public override void NodeBegin(JObject context, JToken delta, JToken left, object key, object leftKey, JsonDiffDeltaType type, JsonDiffNodeType nodeType, bool isLast)
if (type == JsonDiffDeltaType.Modified)
context.Add(key.ToString().ToVariableName(), new JObject());
public override void FormatAdded(JObject context, JToken delta, JToken left, object key = null, object leftKey = null,
Console.WriteLine("Added: " + key);
public override void FormatModified(JObject context, JToken delta, JToken left, object key = null, object leftKey = null,
var prop = (JProperty)context.Last;
var propVal = (JObject)prop.Value;
if (ChangedKeys.Contains(key.ToString()))
propVal.Replace(JObject.FromObject(new
public override void FormatDeleted(JObject context, JToken delta, JToken left, object key = null, object leftKey = null,
Console.WriteLine("Deleted: " + key);
public override void FormatMoved(JObject context, JToken delta, JToken left, object key = null, object leftKey = null,
Console.WriteLine("Moved: " + key);
public override void FormatTextDiff(JObject context, JToken delta, JToken left, object key = null, object leftKey = null,
public override void FormatUnchanged(JObject context, JToken delta, JToken left, object key = null, object leftKey = null,
public override void FormatMoveDestination(JObject context, JToken delta, JToken left, object key = null, object leftKey = null,
Console.WriteLine("Move Destination: " + key);
public enum JsonDiffDeltaType
public enum JsonDiffNodeType
public abstract class JsonDiffFormatter
private JObject _context;
protected List<string> ChangedKeys = new List<string>();
protected JsonDiffFormatter()
public void Format(JToken delta, JToken left)
var castedDelta = (JObject)delta;
ChangedKeys.AddRange(castedDelta.Properties().Select(prop => prop.Name));
var context = JObject.Parse("{}");
Recurse(context, delta, left);
private void Finalize(JObject context)
private JsonDiffDeltaType GetDeltaType(JToken delta, JToken movedFrom)
if (!movedFrom.IsUndefined())
return JsonDiffDeltaType.MoveDestination;
return JsonDiffDeltaType.Unchanged;
if (delta.Type == JTokenType.Array)
return JsonDiffDeltaType.Added;
return JsonDiffDeltaType.Modified;
if (delta.Count() == 3 && delta.Last.Value<int>() == 0)
return JsonDiffDeltaType.Deleted;
if (delta.Count() == 3 && delta.Last.Value<int>() == 2)
return JsonDiffDeltaType.TextDiff;
if (delta.Count() == 3 && delta.Last.Value<int>() == 3)
return JsonDiffDeltaType.Moved;
else if (delta.Type == JTokenType.Object)
return JsonDiffDeltaType.Node;
return JsonDiffDeltaType.Unknown;
private void Recurse(JObject context, JToken delta, JToken left, object key = null, object leftKey = null, JToken movedFrom = null, bool isLast = false)
var useMoveOriginHere = !delta.IsUndefined() && !movedFrom.IsUndefined();
var leftValue = useMoveOriginHere ? movedFrom : left;
if (delta.IsUndefined() && key.IsNull()) return;
var type = GetDeltaType(delta, movedFrom);
var nodeType = type == JsonDiffDeltaType.Node
? (delta["_t"] != null && delta["_t"].Value<string>() == "a"
: JsonDiffNodeType.Object)
: JsonDiffNodeType.Undefined;
NodeBegin(context, delta, left, key, leftKey, type, nodeType, isLast);
RootBegin(context, type, nodeType);
case JsonDiffDeltaType.Node:
FormatNode(context, delta, left, key, leftKey, movedFrom);
case JsonDiffDeltaType.Added:
FormatAdded(context, delta, left, key, leftKey, movedFrom);
case JsonDiffDeltaType.Deleted:
FormatDeleted(context, delta, left, key, leftKey, movedFrom);
case JsonDiffDeltaType.Modified:
FormatModified(context, delta, left, key, leftKey, movedFrom);
case JsonDiffDeltaType.MoveDestination:
FormatMoveDestination(context, delta, left, key, leftKey, movedFrom);
case JsonDiffDeltaType.Moved:
FormatMoved(context, delta, left, key, leftKey, movedFrom);
case JsonDiffDeltaType.TextDiff:
FormatTextDiff(context, delta, left, key, leftKey, movedFrom);
case JsonDiffDeltaType.Unchanged:
FormatUnchanged(context, delta, left, key, leftKey, movedFrom);
case JsonDiffDeltaType.Unknown:
NodeEnd(context, delta, left, key, leftKey, type, nodeType, isLast);
RootEnd(context, type, nodeType);
public virtual void FormatNode(JObject context, JToken delta, JToken left, object key = null, object leftKey = null, JToken movedFrom = null)
var keys = new List<string>();
var castedDelta = (JObject)delta;
keys.AddRange(castedDelta.Properties().Select(prop => prop.Name));
var isArrayKeys = delta["_t"] != null && delta["_t"].Value<string>() == "a";
var moveDestinations = JToken.Parse("{}");
if (left.Type == JTokenType.Array)
var castedArrayLeft = (JArray)left;
for (var i = 0; i < castedArrayLeft.Count; i++)
if (delta[i.ToString()].IsUndefined() && (!isArrayKeys || delta["_" + i].IsUndefined()))
else if (left.Type == JTokenType.Object)
var castedObjectLeft = (JObject) left;
keys.AddRange(from prop in castedObjectLeft.Properties() where delta[prop.Name].IsUndefined() select prop.Name);
foreach (var prop in castedDelta.Properties())
if (val.Type == JTokenType.Array && !val.ElementAtOrDefault(2).IsUndefined() && val.ElementAtOrDefault(2).Value<int>() == 3)
moveDestinations[val[1].Value<string>()] = new JObject(new
value = !left.IsUndefined() ? left[Convert.ToInt32(name.Substring(1))] : null
keys.Sort((a, b) => ArrayKeyToSortNumber(a).CompareTo(ArrayKeyToSortNumber(b)));
keys.Sort((a, b) => string.Compare(a, b, StringComparison.Ordinal));
for (var inx = 0; inx < length; inx++)
if (isArrayKeys && k == "_t")
? (Int32.TryParse(k, out n) ? k : TrimUnderscore(k))
var isLast = inx == length - 1;
, !left.IsUndefined() ? (isArrayKeys ? left[Int32.Parse(lK)] : left[lK]) : null
private double ArrayKeyToSortNumber (string key)
if (key.Substring(0, 1) == "_")
return Convert.ToInt32(key.Substring(1));
return Convert.ToInt32(key) + .1;
private string TrimUnderscore(string str)
return str.Substring(0, 1) == "_" ? str.Substring(1) : str;
public virtual void NodeBegin(JObject context, JToken delta, JToken left, object key, object leftKey, JsonDiffDeltaType type, JsonDiffNodeType nodeType, bool isLast){
public virtual void RootBegin(JObject context, JsonDiffDeltaType type, JsonDiffNodeType nodeType){
public virtual void NodeEnd(JObject context, JToken delta, JToken left, object key, object leftKey, JsonDiffDeltaType type, JsonDiffNodeType nodeType, bool isLast){
public virtual void RootEnd(JObject context, JsonDiffDeltaType type, JsonDiffNodeType nodeType){
public abstract void FormatAdded(JObject context, JToken delta, JToken left, object key = null, object leftKey = null, JToken movedFrom = null);
public abstract void FormatModified(JObject context, JToken delta, JToken left, object key = null, object leftKey = null, JToken movedFrom = null);
public abstract void FormatDeleted(JObject context, JToken delta, JToken left, object key = null, object leftKey = null, JToken movedFrom = null);
public abstract void FormatMoved(JObject context, JToken delta, JToken left, object key = null, object leftKey = null, JToken movedFrom = null);
public abstract void FormatTextDiff(JObject context, JToken delta, JToken left, object key = null, object leftKey = null, JToken movedFrom = null);
public abstract void FormatUnchanged(JObject context, JToken delta, JToken left, object key = null, object leftKey = null, JToken movedFrom = null);
public abstract void FormatMoveDestination(JObject context, JToken delta, JToken left, object key = null, object leftKey = null, JToken movedFrom = null);
public static class Extensions
public static bool IsUndefined(this JToken jToken)
|| (jToken.Type == JTokenType.Array && !jToken.HasValues)
|| (jToken.Type == JTokenType.Object && !jToken.HasValues);
public static bool IsNull<T>(this T value)
public static string ToVariableName(this string input, bool lowerFirstChar = false)
if (string.IsNullOrEmpty(input))
input = input.RemoveSpecialCharacters();
input = input.ToCapitalize();
input = Regex.Replace(input, @"\s", string.Empty);
return input.First().ToString(CultureInfo.InvariantCulture).ToUpper() + input.Substring(1);
public static string ToCapitalize(this string input)
if (string.IsNullOrEmpty(input))
var ca = input.ToCharArray();
foreach (Match m in Regex.Matches(input, @"\b[a-z]"))
ca[m.Index] = Char.ToUpper(ca[m.Index]);
public static string RemoveSpecialCharacters(this string input)
return Regex.Replace(input, @"[^a-zA-Z0-9_.\s]+", string.Empty, RegexOptions.Compiled);