Skip to content

Instantly share code, notes, and snippets.

@cameronism
Last active March 14, 2018 04:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cameronism/6e48d8cf7e98a2d2bd6bc668309757ed to your computer and use it in GitHub Desktop.
Save cameronism/6e48d8cf7e98a2d2bd6bc668309757ed to your computer and use it in GitHub Desktop.
JObject based JSON diff
void Main()
{
var pairs = new (object, object)[]
{
(new {}, new {}),
(new { a = 1 }, new { a = "1" }),
(new { a = "1", b = 2 }, new { a = 1 }),
(new { a = "1", c = 2 }, new { a = 1, c = 2 }),
(new { a = "1", c = new {} }, new { a = 1, c = new {} }),
(new { a = new { a = 1 } }, new { a = new {} }),
(new { a = new[] { 0 } }, new { a = new int[] {} }),
(new { b = true }, new { b = false }),
(new { b = (object)null }, new { b = false }),
};
foreach (var (before, after) in pairs)
{
var a = JsonConvert.SerializeObject(before);
var b = JsonConvert.SerializeObject(after);
new
{
before = a,
after = b,
diff1 = JsonDiff.Diff(a, b).Select(d => new { d.Property, d.OldType, d.Newype, OldValue = d.OldValue?.ToString(), NewValue = d.NewValue?.ToString() }),
diff2 = JsonDiff.Diff(b, a).Select(d => new { d.Property, d.OldType, d.Newype, OldValue = d.OldValue?.ToString(), NewValue = d.NewValue?.ToString() }),
}.Dump();
}
}
internal class JsonDiff
{
public string Property { get; set; }
public JTokenType OldType { get; set; }
public JTokenType Newype { get; set; }
public JToken OldValue { get; set; }
public JToken NewValue { get; set; }
public static List<JsonDiff> Diff(string oldValue, string newValue)
{
var results = new List<JsonDiff>();
Same(JObject.Parse(oldValue), JObject.Parse(newValue), results);
return results;
}
private static bool Same(JToken oldt, JToken newt)
{
if (oldt.Type != newt.Type) return false;
switch (oldt.Type)
{
case JTokenType.Object: return Same((JObject)oldt, (JObject)newt, results: null);
case JTokenType.Array:
var olda = (JArray)oldt;
var newa = (JArray)newt;
return olda.Count == newa.Count &&
Enumerable.Range(0, olda.Count)
.All(i => Same(olda[i], newa[i]));
}
return oldt.Equals(newt);
}
private static bool Same(JObject oldj, JObject newj, List<JsonDiff> results)
{
foreach (var prop in oldj.Properties())
{
var name = prop.Name;
var oldv = prop.Value;
var newv = newj[name];
if (newv == null || !Same(oldv, newv))
{
if (results == null) return false;
results.Add(new JsonDiff
{
Property = name,
OldValue = oldv,
OldType = oldv.Type,
NewValue = newv,
Newype = newv == null ? JTokenType.Undefined : newv.Type,
});
}
}
foreach (var prop in newj.Properties())
{
var name = prop.Name;
var newv = prop.Value;
// just grab the missing things now
if (oldj[name] == null)
{
if (results == null) return false;
results.Add(new JsonDiff
{
Property = name,
NewValue = newv,
Newype = newv.Type,
OldType = JTokenType.Undefined,
});
}
}
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment