Created
May 31, 2012 08:14
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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