using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using JsonDiffPatchDotNet;
public static void Main()
var left = "[{'Key':'Well construction/design data'},{'Key':'Well type','Value':'','Unit':''},{'Key':'Location','Value':'','Unit':''},{'Key':'Design pressure','Value':'','Unit':'Bar'},{'Key':'Maximum expected well head pressure','Value':'','Unit':'Bar'},{'Key':'Maximum expected well head temperature','Value':'750','Unit':'℃'},{'Key':'Cement integrity confidence','Value':'','Unit':''},{'Key':'Well depth (MD-RKB)','Value':'','Unit':'m'},{'Key':'Well depth (TVD-RKB)','Value':'','Unit':'m'},{'Key':'Water depth (Mean sea level (MSL) - Seabed)','Value':'500','Unit':'m'},{'Key':'Reservoir characteristics'},{'Key':'Reservoir pressure','Value':'','Unit':'Bar'},{'Key':'Reservoir temperature','Value':'','Unit':'℃'},{'Key':'Chloride','Value':'','Unit':'ppm'},{'Key':'H2S','Value':'','Unit':'ppm'},{'Key':'CO2','Value':'','Unit':'%'},{'Key':'Operating condition'},{'Key':'Status','Value':'','Unit':''},{'Key':'Medium','Value':'','Unit':''},{'Key':'Sand production','Value':'','Unit':'kg/d'},{'Key':'Pressure cycling (max. variation)','Value':'','Unit':'Bar'},{'Key':'Temperature cycling (max. variation)','Value':'','Unit':'℃'},{'Key':'Downhole safety valve installed','Value':'Yes','Unit':''},{'Key':'Surface Safety valve installed','Value':'Yes','Unit':''},{'Key':'Open flow potential','Value':'','Unit':'m3/d'},{'Key':'Historical data'},{'Key':'Well completed/Tubing installed','Value':'','Unit':''},{'Key':'Well drilled/Production casing installed','Value':'','Unit':''},{'Key':'X-mas tree installed','Value':'','Unit':''},{'Key':'Well plugged and abandoned (P&A)','Value':'','Unit':''}]";
var right = "[{'Key':'Well construction/design data'},{'Key':'Well type','Value':'','Unit':''},{'Key':'Location','Value':'Vietnam','Unit':''},{'Key':'Design pressure','Value':'100','Unit':'Psi'},{'Key':'Maximum expected well head pressure','Value':'','Unit':'Bar'},{'Key':'Maximum expected well head temperature','Value':'1250','Unit':'℃'},{'Key':'Cement integrity confidence','Value':'','Unit':''},{'Key':'Well depth (MD-RKB)','Value':'','Unit':'m'},{'Key':'Well depth (TVD-RKB)','Value':'','Unit':'m'},{'Key':'Water depth (Mean sea level (MSL) - Seabed)','Value':'1500','Unit':'km'},{'Key':'Reservoir characteristics'},{'Key':'Reservoir pressure','Value':'','Unit':'Bar'},{'Key':'Reservoir temperature','Value':'','Unit':'℃'},{'Key':'Chloride','Value':'','Unit':'ppm'},{'Key':'H2S','Value':'','Unit':'ppm'},{'Key':'CO2','Value':'','Unit':'%'},{'Key':'Operating condition'},{'Key':'Status','Value':'','Unit':''},{'Key':'Medium','Value':'','Unit':''},{'Key':'Sand production','Value':'','Unit':'kg/d'},{'Key':'Pressure cycling (max. variation)','Value':'','Unit':'Bar'},{'Key':'Temperature cycling (max. variation)','Value':'','Unit':'℃'},{'Key':'Downhole safety valve installed','Value':'Yes','Unit':''},{'Key':'Surface Safety valve installed','Value':'Yes','Unit':''},{'Key':'Open flow potential','Value':'','Unit':'m3/d'},{'Key':'Historical data'},{'Key':'Well completed/Tubing installed','Value':'','Unit':''},{'Key':'Well drilled/Production casing installed','Value':'','Unit':''},{'Key':'X-mas tree installed','Value':'','Unit':''},{'Key':'Well plugged and abandoned (P&A)','Value':'','Unit':''}]";
var jLeft = JArray.Parse(left);
var jRight = JArray.Parse(right);
var adapter = new WellDesignDataJsonDiffAdapter();
var jexecLeft = adapter.Define(jLeft);
var jexecRight = adapter.Define(jRight);
var diffPatcher = new JsonDiffPatcher(jexecLeft, jexecRight);
var delta = diffPatcher.Diff();
var formatter = new WellDesignDataJsonDiffFormatter(delta, jexecLeft);
Console.WriteLine(formatter.Context);
public class JsonDiffPatcher
public JsonDiffPatcher(JToken left, JToken right)
var jdp = new JsonDiffPatch();
var diff = jdp.Diff(_left, _right);
public class WellDesignDataJsonDiffFormatter : JsonDiffFormatter
public WellDesignDataJsonDiffFormatter(JToken delta, JToken left) : base(delta, left)
public override void NodeBegin(JObject context, object key, object leftKey, JsonDiffDeltaType type, JsonDiffNodeType nodeType, bool isLast)
if(type == JsonDiffDeltaType.Node){
context.Add(key.ToString(), 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("Value".Equals(key.ToString())) {
propVal.Add("ValueChange", JObject.FromObject(new {
}else if("Unit".Equals(key.ToString())) {
propVal.Add("UnitChange", 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 JsonDiffFormatter(JToken delta, JToken left)
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"].IsUndefined() && delta["_t"].HasValues && delta["_t"].Value<string>() == "a"
: JsonDiffNodeType.Object)
: JsonDiffNodeType.Undefined;
NodeBegin(context, 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, 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"].IsUndefined() && delta["_t"].HasValues && 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].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;
Recurse(context, delta[k], !left.IsUndefined() ? left[lK] : null, k, lK, moveDestinations[lK], isLast);
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, 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, 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 interface IJsonDiffAdapter
JToken Define(JToken obj);
public class WellDesignDataJsonDiffAdapter : IJsonDiffAdapter
public JToken Define(JToken obj)
var props = obj.ToDictionary(x => x.Value<string>("Key"), x => (object)new { Value = x.Value<object>("Value"), Unit = x.Value<object>("Unit") });
var jObj = JObject.FromObject(props);
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)