Skip to content

Instantly share code, notes, and snippets.

@lanwin
Created May 31, 2012 08:14
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 lanwin/2841822 to your computer and use it in GitHub Desktop.
Save lanwin/2841822 to your computer and use it in GitHub Desktop.
ObjectDiff to compare objects with checking if properties, fields, and arrays are the same
static class ObjectDiff
{
static IEnumerable<Member> GetMembers(object obj)
{
foreach(var propertyInfo in obj.GetType().GetProperties().Where(p => p.CanRead))
{
var info = propertyInfo;
yield return new Member
{
Name = info.Name,
GetValue = () => info.GetValue(obj, null)
};
}
foreach(var fieldInfo in obj.GetType().GetFields())
{
var info = fieldInfo;
yield return new Member
{
Name = info.Name,
GetValue = () => info.GetValue(obj)
};
}
}
static IEnumerable<Node> FlattenGraph(object value, string fullName = "", List<object> knownObjects = null)
{
knownObjects = knownObjects ?? new List<object>();
var array = new ArrayList();
var members = new Member[0];
if(value != null && Convert.GetTypeCode(value) == TypeCode.Object)
if(value.GetType().IsArray)
foreach(var element in (IEnumerable)value)
array.Add(element);
else
members = GetMembers(value).ToArray();
if(fullName.Length > 0)
yield return new Node
{
FullName = fullName,
Value = value,
HasChildren = array.Count > 0 || members.Length > 0
};
if(knownObjects.Contains(value))
yield break;
knownObjects.Add(value);
var index = 0;
foreach(var arrayValue in array)
{
var arrayName = string.Format("{0}[{1}]", fullName, index++);
foreach(var node in FlattenGraph(arrayValue, arrayName, knownObjects))
yield return node;
}
foreach(var member in members)
{
var memberValue = member.GetValue();
var memberFullName = ( fullName.Length > 0 ? fullName + "." : "" ) + member.Name;
foreach(var memberNode in FlattenGraph(memberValue, memberFullName, knownObjects))
yield return memberNode;
}
}
public static IEnumerable<DiffResult> Run(object a, object b)
{
var membersA = FlattenGraph(a);
var membersB = FlattenGraph(b);
foreach(var tuple in Zip(membersA, membersB))
{
string name = null;
object leftValue = Missing.Value;
var leftHasChildren = false;
object rightValue = Missing.Value;
var rightHasChildren = false;
if(tuple.Item1 != null)
{
name = tuple.Item1.FullName;
leftValue = tuple.Item1.Value;
leftHasChildren = tuple.Item1.HasChildren;
}
if(tuple.Item2 != null)
{
name = tuple.Item2.FullName;
rightValue = tuple.Item2.Value;
rightHasChildren = tuple.Item2.HasChildren;
}
if(leftHasChildren && rightHasChildren)
continue;
yield return new DiffResult
{
Name = name,
LeftValue = leftValue,
RightValue = rightValue,
AreDifferent = !Equals(leftValue, rightValue)
};
}
}
static IEnumerable<Tuple<Node, Node>> Zip(IEnumerable<Node> left, IEnumerable<Node> right)
{
var leftList = left.ToList();
var rightList = right.ToList();
while(leftList.Count > 0)
{
var leftItem = leftList[0];
leftList.RemoveAt(0);
var rightItem = rightList.Find(r => r.FullName == leftItem.FullName);
if(rightItem == null)
{
leftList.RemoveAll(n => n.FullName.StartsWith(leftItem.FullName));
yield return Tuple.Create(leftItem, (Node)null);
continue;
}
rightList.Remove(rightItem);
yield return Tuple.Create(leftItem, rightItem);
}
while(rightList.Count > 0)
{
var rightItem = rightList[0];
rightList.RemoveAt(0);
var leftItem = leftList.Find(r => r.FullName == rightItem.FullName);
if(leftItem == null)
{
rightList.RemoveAll(n => n.FullName.StartsWith(rightItem.FullName));
yield return Tuple.Create((Node)null, rightItem);
continue;
}
yield return Tuple.Create(leftItem, rightItem);
}
}
public class DiffResult
{
public string Name { get; set; }
public object LeftValue { get; set; }
public object RightValue { get; set; }
public bool AreDifferent { get; set; }
}
class Member
{
public string Name { get; set; }
public Func<object> GetValue { get; set; }
}
class Node
{
public bool HasChildren { get; set; }
public string FullName { get; set; }
public object Value { get; set; }
}
}
class Program
{
static void Main()
{
var expected = new
{
a = 2,
b = "test",
x = new[] {"a", "b"},
c = new {z = "barf", b = "faz"}
};
var actual = new
{
a = 2,
b = "test3",
x = new[] {"a", "b", "c"},
c = new {b = "maf", u = "ugh"}
};
var results = ObjectDiff.Run(expected, actual);
foreach(var diffResult in results)
{
Console.Write(diffResult.Name);
Console.Write(" ");
if(diffResult.AreDifferent == false)
Console.Write("same");
else if(diffResult.LeftValue == Missing.Value)
Console.Write("isnt expected");
else if(diffResult.RightValue == Missing.Value)
Console.Write("expected but missing");
else
Console.Write("expected '" + diffResult.LeftValue + "' but got '" + diffResult.RightValue + "'");
Console.WriteLine();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment