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 Diff(string oldValue, string newValue) { var results = new List(); 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 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; } }