using System.Collections.Generic;
using Newtonsoft.Json.Linq;
public static void Main()
var n1 = new { A = 1, B = 2, C = new[] { 1, 2 } };
var n2 = new { A = 1, B = 2, C = new[] { 0, 2 } };
var res = JsonComparer.Compare(n1, n2);
Console.WriteLine(string.Join("\r\n", res.Changes.Select(_ => $"{_.Path} = {_.Val1} {_.Val2}")));
Console.WriteLine("No changes");
public class AgreementRelated : Agreement
public Guid? BaseGuid { get; set; }
public string Number { get; set; }
public bool IsEnterprise { get; set; }
public Guid Guid { get; set; }
public Guid? CrmGuid { get; set; }
public string StartDate { get; set; }
public string PlannedEndDate { get; set; }
public decimal Amount { get; set; }
public string TransferDate { get; set; }
public decimal? StartAmount { get; set; }
public static class RelatedMessageExtension
public static IEnumerable<T> Chain<T>(this List<T> list, Guid? baseUid) where T : AgreementRelated
int chainLength = list.Count;
var next = list.FirstOrDefault(_ => _.Guid == baseUid);
if(next is null) yield break;
public static class JsonComparer
public static JsonCompareResult Compare(object obj1, object obj2)
var tokens1 = AsEquatableTokens(obj1).ToDictionary(_ => _.Path);
var tokens2 = AsEquatableTokens(obj2).ToDictionary(_ => _.Path);
var changes1 = GetChanges(tokens1, tokens2);
var changes2 = GetChanges(tokens2, tokens1);
return new JsonCompareResult() { Changes = changes1.Concat(changes2).Distinct().ToList() };
private static IEnumerable<JsonChange> GetChanges(
Dictionary<string, JsonTokenEquatable> tokens1,
Dictionary<string, JsonTokenEquatable> tokens2) =>
tokens1.Values.Except(tokens2.Values).Select(_ => new JsonChange
Val1 = tokens1.ContainsKey(_.Path) ? tokens1[_.Path].Val : null,
Val2 = tokens2.ContainsKey(_.Path) ? tokens2[_.Path].Val : null,
private static HashSet<JTokenType> ValueTokens = new HashSet<JTokenType>
private static IEnumerable<JsonTokenEquatable> AsEquatableTokens(object obj)
var token = JToken.FromObject(obj);
foreach (var valueToken in ReadAllValues(token))
yield return new JsonTokenEquatable(valueToken);
private static IEnumerable<JToken> ReadAllValues(JToken token)
foreach(var subtoken in token)
bool isValueToken = ValueTokens.Contains(subtoken.Type);
foreach (var item in ReadAllValues(subtoken))
private class JsonTokenEquatable : IEquatable<JsonTokenEquatable>
public JsonTokenEquatable(JToken token)
public string Path { get; set; }
public string Val { get; set; }
public override bool Equals(object obj)
return obj is JsonTokenEquatable equitable &&
Path == equitable.Path &&
public bool Equals(JsonTokenEquatable other) => Path == other.Path && Val == other.Val;
public override int GetHashCode() => HashCode.Combine(Path, Val);
public override string ToString() => $"{Path}={Val}";
public string Path { get; set; }
public string Val1 { get; set; }
public string Val2 { get; set; }
public override bool Equals(object obj) => obj is JsonChange change && Path == change.Path;
public override int GetHashCode() => Path?.GetHashCode() ?? 0;
public class JsonCompareResult
public bool HasChanges => Changes?.Any() ?? false;
public List<JsonChange> Changes { get; set; } = new List<JsonChange>();