Giter Site home page Giter Site logo

replaysmike / anydiff Goto Github PK

View Code? Open in Web Editor NEW
112.0 7.0 10.0 209 KB

A CSharp (C#) diff library that allows you to diff two objects and get a list of the differences back.

License: Other

C# 100.00%
diff dotnet-core dotnet-standard csharp csharp-code difference-generator differences-detected difference-between difference

anydiff's Introduction

AnyDiff

nuget nuget Build status Codacy Badge Codacy Badge

A CSharp (C#) diff library that allows you to diff two objects and get a list of the differences back.

Description

AnyDiff works with complex objects of any type, and is great for performing changeset tracking, auditing, or anything else that might require comparing differences between complex objects. Great when combined with AnyClone, as it lets you take a snapshot of the current state of an object and then compare it in the future for changes to it.

It can even do this for objects that are of different types, though the results may vary depending on how different they are.

Installation

Install AnyDiff from the Package Manager Console:

PM> Install-Package AnyDiff

Usage

Comparing two objects with no differences:

using AnyDiff;

var object1 = new MyComplexObject(1, "A string");
var object2 = new MyComplexObject(1, "A string");
var diff = AnyDiff.Diff(object1, object2);
Assert.AreEqual(diff.Count, 0);

Alternate extension syntax is also available (we will use this in further examples):

using AnyDiff.Extensions;

var object1 = new MyComplexObject(1, "A string");
var object2 = new MyComplexObject(1, "A string");
var diff = object1.Diff(object2);
Assert.AreEqual(diff.Count, 0);

Comparing two objects with a single expected change:

var object1 = new MyComplexObject(1, "A string");
var object2 = new MyComplexObject(1, "A different string");
var diff = object1.Diff(object2);
Assert.AreEqual(diff.Count, 1);

Comparing objects using custom Type Converters for proper delta detection:

public class MyComplexObject
{
  public int Id { get; private set; }

  /// <summary>
  /// Convert a formatted string as a TimeSpan
  /// </summary>
  [TypeConverter(typeof(TimeSpanConverter))]
  public string StartTime { get; set; }

  public TypeConverterObject(int id, string startTime)
  {
    Id = id;
    StartTime = startTime;
  }
}

var object1 = new MyComplexObject(1, "04:00:00");
var object2 = new MyComplexObject(1, "04:05:00");
var diff = object1.Diff(object2);
Assert.AreEqual(diff.Count, 1);
Assert.AreEqual(TimeSpan.FromMinutes(5), diff.First().Delta); // difference of 5 minutes

Ignoring Properties

Anydiff will ignore fields and properties decorated using attributes: [IgnoreDataMember], [NonSerialized], and [JsonIgnore]. In addition, you can specify properties to ignore using expression syntax. See Ignoring Properties and Fields for more details.

Ignoring by properties explicitly by passing a list of properties via expressions:

var object1 = new MyComplexObject(1, "A string", true);
var object2 = new MyComplexObject(2, "A different string", true);
var diff = object1.Diff(object2, x => x.Id, x => x.Name);
Assert.AreEqual(diff.Count, 0);

Diff specified properties only

AnyDiff also supports processing of specific properties if you don't want to diff the entire object. This works using the same syntax as ignoring properties but passing a different ComparisonOptions. In the example below, only the properties Id and Name will be compared.

var object1 = new MyComplexObject(1, "A string", true);
var object2 = new MyComplexObject(2, "A different string", true);
var diff = object1.Diff(object2, ComparisonOptions.All | ComparisonOptions.IncludeList, x => x.Id, x => x.Name);
Assert.AreEqual(diff.Count, 0);

Comparing Unordered lists

If you wish to perform a diff that ignores the ordering of data in a collection/list, you can specify that behavior with a ComparisonOptions AllowCollectionsToBeOutOfOrder flag as well as the AllowEqualsOverride, as seen below.

var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 1, 3, 2 };
var diff = list1.Diff(list2, ComparisonOptions.All | ComparisonOptions.AllowCollectionsToBeOutOfOrder | ComparisonOptions.AllowEqualsOverride);
Assert.AreEqual(diff.Count, 0);

Analyzing results

Viewing the results of a diff:

var diff = object1.Diff(object2);

foreach(var difference in diff)
{
  Console.Write($"Index: {difference.ArrayIndex}"); // when array elements differ in value
  Console.Write($"Delta: {difference.Delta}"); // when numbers, Dates, Timespans, strings differ in value
  Console.Write($"Left: {difference.LeftValue}"); // the left value being compared
  Console.Write($"Right: {difference.RightValue}"); // the right value being compared
  Console.Write($"Property name: {difference.Property}"); // the name of the field/property
  Console.Write($"Property type: {difference.PropertyType}"); // the type of the field/property
}

Scenarios supported

  • Circular references
  • Using TypeConverters to understand data types
  • Deltas on strings, DateTimes, TimeSpans, numeric types
  • Comparing arrays, collections, custom collections, dictionaries, hashtables
  • Comparing collections with different ordering
  • Complex objects, deep type inspection
  • Entity Framework objects
  • IEquatable support

Using with other libraries

Comparing the difference between the same object at different states, using AnyClone

using AnyDiff.Extensions;
using AnyClone;

var object1 = new MyComplexObject(1, "A string");
var object1Snapshot = object1.Clone();

var diff = object1.Diff(object1Snapshot);
Assert.AreEqual(diff.Count, 0);

// change something anywhere in the object tree
object1.Name = "A different string";

diff = object1.Diff(object1Snapshot);
Assert.AreEqual(diff.Count, 1);

anydiff's People

Contributors

colinbull avatar itnmike avatar jtone123 avatar replaysmike avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

anydiff's Issues

System.NotSupportedException thrown for AllowCollectionsToBeOutOfOrder flag

var diff = list1.Diff(list2, ComparisonOptions.All | ComparisonOptions.AllowCollectionsToBeOutOfOrder | ComparisonOptions.AllowEqualsOverride);

throws System.NotSupportedException pointing at IEnumerator.Reset() . I want to ignore ordering differences in lists. I tried igoring, but still getting it. Could you check this one please ? Version is 1.0.88

[IgnoreDataMember]
[JsonIgnore]
public virtual IEnumerable<string> SomeEnumerableIds
{
     get { yield return this.someStringId; }
}

System.NotSupportedException: Specified method is not supported.
at ***********.****Entity.get_SomeEnumerableIds()+
System.Collections.IEnumerator.Reset()
at AnyDiff.DiffProvider.GetDifferences(String propertyName, Type propertyType, TypeConverter typeConverter, IEnumerable1 attributes, Object left, Object right, Object parent, List1 differences, Int32 currentDepth, Int32 maxDepth, <37c2b6f9-1728-45df-bb6e-473d4690fe19>ObjectHashcodeMap objectTree, String path, ComparisonOptions options, ICollection1 propertiesToExcludeOrInclude, DiffOptions diffOptions) at AnyDiff.DiffProvider.RecurseProperties(Object left, Object right, Object parent, List1 differences, Int32 currentDepth, Int32 maxDepth, <37c2b6f9-1728-45df-bb6e-473d4690fe19>ObjectHashcodeMap objectTree, String path, ComparisonOptions comparisonOptions, ICollection1 propertiesToExcludeOrInclude, DiffOptions diffOptions) at AnyDiff.DiffProvider.ComputeDiff[T](T left, T right, Int32 maxDepth, ComparisonOptions comparisonOptions, DiffOptions diffOptions, Expression1[] propertiesToExcludeOrInclude)
at AnyDiff.Extensions.Extensions.Diff[T](T left, T right, Int32 maxDepth, ComparisonOptions comparisonOptions, DiffOptions diffOptions, Expression1[] propertiesToExcludeOrInclude) at AnyDiff.Extensions.Extensions.Diff[T](T left, T right, ComparisonOptions comparisonOptions, Expression1[] propertiesToExcludeOrInclude)

Bug where AnyDiff recurses properties of a collection

It seems in some specific circumstance it's possible AnyDiff will recurse the properties of a collection, which shouldn't happen. It should only analyze the contents of a collection, it's possible that it needs to switch to using TypeSupport for some of that logic.

Will fix and cleanup.

Comparing objects of different types not working

Hi, let's say I have an object of type Person that is base object and has two child types: Employee and Candidate. Now when comparing Employee to Candidate type I use following overload of Diff method:

var changes = source.Diff(destination, ComparionOptions.AllowCompareDifferentObjects, IgnoreProperties.ConfigureForSimpleTypes()); ( where IgnoreProperties is just a static helper class that returns an array of properties to ignore)

I can clearly see that source.FirstName and destination.FirstName are not equal, yet changes are empty every time. Both objects inherit common properties from Person. They have their own specific properties, but that was something I didn't tested. Any thought on this?

Thanks.

How to speed up comparisons?

  1. I am getting an error that says two objects are not the same type - it would be useful when comparing deeply nested object graphs to know what types and property names are being compared.
  2. Hitting Pause in the debugger reveals some somewhat surprising hotspots
  3. What if there was a C# Source Generator for comparisons?

In my example case, I am talking to a FinTech gRPC API and trying to validate that post-save security response I get back from the vendor is the same as the response I get from fetching the latest version. For some reason, I had to add ComparisonOptions.AllowCompareDifferentObjects even though the top-level objects are the same gRPC object. I'm assuming that somewhere in my sub-graph, I have a polymorphic collection that contains a different sub-type, .e.g.

message Value {
  oneof oneOfValue {
    string stringValue = 1;
    bool boolValue = 2;
    int32 intValue = 3;
    int64 longValue = 4;
    double doubleValue = 5;
    CustomTypedValue customTypedValue = 6;
  }
}

Below is my example code - it has been running for at least 8 minutes just to compare one relatively tiny object graph, on .NET Framework 4.8.2. - Maybe the issue here is with slow reflection on an older framework, but I was not expecting it to be this slow.

var comparisonResult = AnyDiff.AnyDiff.Diff(savedSecurity, latestSecurity, ComparisonOptions.CompareProperties | ComparisonOptions.CompareCollections | ComparisonOptions.AllowCompareDifferentObjects);

License

hi,
What is the license of the library? github shows GPL 3.0 license but nuget package is AGPL 3.0. Which one is correct?

Weird Output (Private fields?)

Is there a way to get a list of all the properties before the compare. Im not sure where the "objectIDField" is coming from. It looks like its a private field. Is there a way to not compare the privates?

Property name: ObjectID
Property type: System.String
Left 110007295: 7487b8ef-575e-45d8-9f1d-5cfe86323276
Right 110006740: f046b926-da60-4654-92e1-fb0f3b2e0b11
Property name: objectIDField
Property type: System.String
Left 110007295: 7487b8ef-575e-45d8-9f1d-5cfe86323276
Right 110006740: f046b926-da60-4654-92e1-fb0f3b2e0b11

 private string objectIDField;
[XmlElement(IsNullable = true, Order = 6)]
    public string ObjectID
    {
      get
      {
        return this.objectIDField;
      }
      set
      {
        this.objectIDField = value;
      }
    }

How to find differences that are nested deep inside a property?

How to find differences that are nested deep inside a property?
In the classes mentioned below, how do I get the differences which are listed 2 level deep e.g., Cool.Author.Address.City and Cool.Author.Address.Country

 public class Cool
    {
        public int id;
        public int revision;
        public string[] vehicles;
        public Author author;
        public string tagline;
        public Gender gender;
    }

    public class Author
    {
        public string FirstName;
        public string LastName;
        public Address Address;
    }

    public class Address
    {
        public string City;
        public string Country;
    }

exception handling in GetDifferences

I'm using AnyDiff to compare instances of the type Microsoft.TeamFoundation.Build.WebApi.BuildDefinition from assembly Microsoft.TeamFoundation.Build2.WebApi (Version=16.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a). In certain cases (I could not really understand why), the process breaks by a System.NotSupportedException:

System.NotSupportedException: Specified method is not supported.
   at System.Reflection.TypeInfo.<get_DeclaredNestedTypes>d__23.System.Collections.IEnumerator.Reset()
   at AnyDiff.DiffProvider.GetCountFromEnumerable(IEnumerable enumerable)
   at AnyDiff.DiffProvider.GetDifferences(String propertyName, Type propertyType, TypeConverter typeConverter, Object left, Object right, Object parent, ICollection``1 differences, Int32 currentDepth, Int32 maxDepth, ObjectHashcodeMap objectTree, String path, ComparisonOptions options, ICollection``1 propertyList, DiffOptions diffOptions)
   at AnyDiff.DiffProvider.RecurseProperties(Object left, Object right, Object parent, ICollection``1 differences, Int32 currentDepth, Int32 maxDepth, ObjectHashcodeMap objectTree, String path, ComparisonOptions comparisonOptions, ICollection``1 propertyList, DiffOptions diffOptions)
   at AnyDiff.DiffProvider.GetDifferences(String propertyName, Type propertyType, TypeConverter typeConverter, Object left, Object right, Object parent, ICollection``1 differences, Int32 currentDepth, Int32 maxDepth, ObjectHashcodeMap objectTree, String path, ComparisonOptions options, ICollection``1 propertyList, DiffOptions diffOptions)
   at AnyDiff.DiffProvider.RecurseProperties(Object left, Object right, Object parent, ICollection``1 differences, Int32 currentDepth, Int32 maxDepth, ObjectHashcodeMap objectTree, String path, ComparisonOptions comparisonOptions, ICollection``1 propertyList, DiffOptions diffOptions)
...

I suggest to add a try/catch block to explain the path causing the not expected behavior, so that one can add it to the list of exceptions.

Path is missing the array index

Hello there, I'm comparing these two objects:

var obj1 = new Projection
{
    Speakers = new [] { new Speaker { Id = 1, Name = "Fizz" } }
};

var obj2 = new Projection
{
    Speakers = new [] { new Speaker { Id = 1, Name = "Buzz" } }
};

var diffs = AnyDiff.Diff(obj1, obj2);

As you can see, The Name property of the first Speaker in the array has a different value.
The resulting Difference object is omitting the 0 index in the path. It's .Speakers.Name but it should be .Speakers[0].Name.
speakers
Without an index in the Path, I can't really tell which of the objects in the array changed.

Thanks,
Moreno

Specified method is not supported

System.NotSupportedException: Specified method is not supported. at Newtonsoft.Json.Linq.JProperty.JPropertyList.<GetEnumerator>d__1.System.Collections.IEnumerator.Reset() at ObjectCompare.DiffProvider.GetCountFromEnumerable(IEnumerable enumerable) at ObjectCompare.DiffProvider.GetDifferences(String propertyName, Type propertyType, TypeConverter typeConverter, Object left, Object right, Object parent, ICollection1 differences, Int32 currentDepth, Int32 maxDepth, ObjectHashcodeMap objectTree, String path, ComparisonOptions options, ICollection1 propertyList) at ObjectCompare.DiffProvider.RecurseProperties(Object left, Object right, Object parent, ICollection1 differences, Int32 currentDepth, Int32 maxDepth, ObjectHashcodeMap objectTree, String path, ComparisonOptions options, ICollection1 propertyList) at ObjectCompare.DiffProvider.GetDifferences(String propertyName, Type propertyType, TypeConverter typeConverter, Object left, Object right, Object parent, ICollection1 differences, Int32 currentDepth, Int32 maxDepth, ObjectHashcodeMap objectTree, String path, ComparisonOptions options, ICollection1 propertyList) at ObjectCompare.DiffProvider.RecurseProperties(Object left, Object right, Object parent, ICollection1 differences, Int32 currentDepth, Int32 maxDepth, ObjectHashcodeMap objectTree, String path, ComparisonOptions options, ICollection1 propertyList)

The issue is happening when you have different objects inherited from one parent object and we put them in the list (List) container.

    private static long GetCountFromEnumerable(IEnumerable enumerable)
    {
        if (enumerable == null)
            return 0L;
        var count = 0L;
        var enumerableType = enumerable.GetType().GetExtendedType(DefaultTypeSupportOptions);
        if (enumerableType.IsCollection)
            return ((ICollection)enumerable).Count;
        if (enumerableType.IsArray)
            return ((Array)enumerable).LongLength;
       if (enumerableType.IsEnumerable)
        {
            if (enumerable is IEnumerable<Newtonsoft.Json.Linq.JToken> jPropertyList)
            {
                return jPropertyList.Count();  // I added this line to fix the issue
            }
        }

        // count the enumerable
        foreach (var row in enumerable)
            count++;

        // try to reset the enumerable, if IEnumerable implements it
        var enumerator = enumerable.GetEnumerator();
        try
        {
            enumerator.Reset();
        }
        catch (NotImplementedException) { }
        return count;
    }

Infinite loop when object contains a reference to Net Type.

This example will course a endless loop on private properties of Type.
Is this intended behavior? How do I fix this.

class TypeReference
{
private Type   _type;
public  string AssemblyQualifiedName { get; set; }

public Type GetTypeInt() => _type;
public TypeReference(Type type)
{
    _type   = type;
    AssemblyQualifiedName = type.AssemblyQualifiedName;
}
}

class StaticValueProvider
{
public TypeReference Source { get; set; }
public object       Value  { get; set; }
}

var staticValueProvider = new StaticValueProvider()
{
Source = new TypeReference(typeof(string)),
Value  = "wa"
};


var staticValueProvider2 = new StaticValueProvider()
{
Source = new TypeReference(typeof(string)),
Value  = "wa"
};

var differences1 = staticValueProvider.Diff(staticValueProvider2);

If I do this comparison it works, but fields can be public.

  var differences1 = staticValueProvider.Diff(staticValueProvider2,
                                              ComparisonOptions.ExcludeList |
                                              ComparisonOptions.CompareCollections |
  //                                            ComparisonOptions.CompareFields |
                                              ComparisonOptions.CompareProperties);

Add Ability to Diff Against NULL?

Firstly, thanks for the great library, I've just found it yesterday and have found it very useful and configurable so far!

One use case I have that it doesn't seem to cater for however is the ability for one (or arguably both) of the objects to be diff'ed to be null. I am using AnyDiff to identify differences between a source and target object for both insert and update operations against my data store. In the case of inserts, my 'source' object will be null and it would be extremely useful for AnyDiff.Diff() to return an ICollection<Differences> which includes all fields marked for comparison in the target object and their values in RightValue and I suppose null (or the most suitable 'empty' value given the fields data type) in LeftValue.

I cannot see anyway of achieving this currently unless I am missing something? I've tried markiny my object for comparison as nullable via T? in the types method call below:

ICollection<Difference> differences = AnyDiff.AnyDiff.Diff<User?>(
    existingUser,
    currentUser,
    ComparisonOptions.All | ComparisonOptions.AllowCollectionsToBeOutOfOrder,
    new string[] { "Id", "CreatedAt", "UpdatedAt" }
);

but this results in:

System.NullReferenceException: Object reference not set to an instance of an object.

Further stack trace:

System.NullReferenceException: Object reference not set to an instance of an object.
at TypeSupport.Extensions.<fd5fea14-b6d2-4046-8d81-b8115045ec3c>ObjectExtensions.GetProperties(Object obj, <4d7a44c8-b5f5-44bf-8471-f0980838d61e>PropertyOptions options)
at AnyDiff.DiffProvider.RecurseProperties(Object left, Object right, Object parent, List`1 differences, Int32 currentDepth, Int32 maxDepth, <37c2b6f9-1728-45df-bb6e-473d4690fe19>ObjectHashcodeMap objectTree, String path, ComparisonOptions comparisonOptions, ICollection`1 propertiesToExcludeOrInclude, DiffOptions diffOptions)
at AnyDiff.DiffProvider.ComputeDiff(Object left, Object right, Int32 maxDepth, ComparisonOptions comparisonOptions, DiffOptions diffOptions, String[] propertiesToExcludeOrInclude)
at AnyDiff.AnyDiff.Diff[T](T left, T right, ComparisonOptions comparisonOptions, String[] propertiesToExcludeOrInclude)

My best idea for a workaround currently would be to create additional classes with nullable and/or sane default fields e.g. BlankUser, enabling the AllowCompareDifferentObjects option and passing this in as my source in these scenarios.

If however, AnyDiff could allow for a null left value however, that would be great?

Add a new attribute to avoid diff

Hi,
Would it be possible to have a new attribute to avoid diff? I'm using the JSON serializer and I cannot use the dedicated attribute due to this.
Thanks

Cannot use AnyDiff within strong-named project.

I want to use AnyDiff within a project that is strong-named. When I try to, I get this exception at runtime.

System.IO.FileLoadException: Could not load file or assembly 'AnyDiff, Version=1.0.83.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. A strongly-named assembly is required. (Exception from HRESULT: 0x80131044)

Because AnyDiff is not strong-named, I cannot use it within this project.

See a similar change made for auth0.net at auth0/auth0.net#281 and Microsoft's recommendation for publicly published .NET libraries here.

System.Reflection.TargetParameterCountException

Hi. Out of the blue, now I get exception: System.Reflection.TargetParameterCountException with message: Parameter count mismatch. The library was working fine for couple of months, but now these are thrown when comparing custom types, and collections of custom types. Nothing changed when it comes to the part that is comparing objects. I don't understand what could cause this.

output window:

Exception thrown: 'System.Reflection.TargetParameterCountException' in System.Private.CoreLib.dll
Exception thrown: 'System.Reflection.TargetParameterCountException' in TypeSupport.dll

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.