29

Given I have two c# objects of the same type, I want to compare them to create a JsonPatchDocument.

I have a StyleDetail class defined like this:

public class StyleDetail
    {
        public string Id { get; set; }
        public string Code { get; set; }
        public string Name { get; set; }
        public decimal OriginalPrice { get; set; }
        public decimal Price { get; set; }
        public string Notes { get; set; }
        public string ImageUrl { get; set; }
        public bool Wishlist { get; set; }
        public List<string> Attributes { get; set; }
        public ColourList Colours { get; set; }
        public SizeList Sizes { get; set; }
        public ResultPage<Style> Related { get; set; }
        public ResultPage<Style> Similar { get; set; }
        public List<Promotion> Promotions { get; set; }
        public int StoreStock { get; set; }
        public StyleDetail()
        {
            Attributes = new List<string>();
            Colours = new ColourList();
            Sizes = new SizeList();
            Promotions = new List<Promotion>();
        }
    }

if I have two StyleDetail objects

StyleDetail styleNew = db.GetStyle(123);
StyleDetail styleOld = db.GetStyle(456);

I now want to create a JsonPatchDocument so I can send the differences to my REST API... How to do this??

JsonPatchDocument patch = new JsonPatchDocument();
// Now I want to populate patch with the differences between styleNew and styleOld - how?

in javascript, there is a library to do this https://www.npmjs.com/package/rfc6902

Calculate diff between two objects:

rfc6902.createPatch({first: 'Chris'}, {first: 'Chris', last: 'Brown'});

[ { op: 'add', path: '/last', value: 'Brown' } ]

but I am looking for a c# implementation

jmc
  • 1,058
  • 1
  • 12
  • 20
  • 1
    I know this is a bit old now ... but did you ever figure out how to do this? I'm looking for the exact same thing! – brazilianldsjaguar Apr 24 '18 at 21:25
  • You could use reflection to iterate through the properties and compare them. See this question for an example of iterating properties: https://stackoverflow.com/questions/1198886/c-sharp-using-reflection-to-copy-base-class-properties – Jack A. Apr 24 '18 at 21:48

3 Answers3

35

Let's abuse the fact that your classes are serializable to JSON! Here's a first attempt at a patch creator that doesn't care about your actual object, only about the JSON representation of that object.

public static JsonPatchDocument CreatePatch(object originalObject, object modifiedObject)
{
    var original = JObject.FromObject(originalObject);
    var modified = JObject.FromObject(modifiedObject);

    var patch = new JsonPatchDocument();
    FillPatchForObject(original, modified, patch, "/");

    return patch;
}

static void FillPatchForObject(JObject orig, JObject mod, JsonPatchDocument patch, string path)
{
    var origNames = orig.Properties().Select(x => x.Name).ToArray();
    var modNames = mod.Properties().Select(x => x.Name).ToArray();

    // Names removed in modified
    foreach (var k in origNames.Except(modNames))
    {
        var prop = orig.Property(k);
        patch.Remove(path + prop.Name);
    }

    // Names added in modified
    foreach (var k in modNames.Except(origNames))
    {
        var prop = mod.Property(k);
        patch.Add(path + prop.Name, prop.Value);
    }

    // Present in both
    foreach (var k in origNames.Intersect(modNames))
    {
        var origProp = orig.Property(k);
        var modProp = mod.Property(k);

        if (origProp.Value.Type != modProp.Value.Type)
        {
            patch.Replace(path + modProp.Name, modProp.Value);
        }
        else if (!string.Equals(
                        origProp.Value.ToString(Newtonsoft.Json.Formatting.None),
                        modProp.Value.ToString(Newtonsoft.Json.Formatting.None)))
        {
            if (origProp.Value.Type == JTokenType.Object)
            {
                // Recurse into objects
                FillPatchForObject(origProp.Value as JObject, modProp.Value as JObject, patch, path + modProp.Name +"/");
            }
            else
            {
                // Replace values directly
                patch.Replace(path + modProp.Name, modProp.Value);
            }
        }       
    }
}

Usage:

var patch = CreatePatch(
    new { Unchanged = new[] { 1, 2, 3, 4, 5 }, Changed = "1", Removed = "1" },
    new { Unchanged = new[] { 1, 2, 3, 4, 5 }, Changed = "2", Added = new { x = "1" } });

// Result of JsonConvert.SerializeObject(patch)
[
  {
    "path": "/Removed",
    "op": "remove"
  },
  {
    "value": {
      "x": "1"
    },
    "path": "/Added",
    "op": "add"
  },
  {
    "value": "2",
    "path": "/Changed",
    "op": "replace"
  }
]
gnud
  • 77,584
  • 5
  • 64
  • 78
  • By the way - you can use the exact same code to diff to JSON strings as well, just create JObjects from the strings and then call `FillPatchForObject` – gnud Apr 24 '18 at 22:02
  • Thank you! Big help. But it does not work correctly if we change "Colours", for example, in the "StyleDetail" class. – Manuel Quelhas Mar 11 '19 at 19:04
  • Is "StyleDetail" being serialized correctly to JSON, if you serialize it directly? Otherwise, my method won't work. Can you give the definition of StyleDetail, and two example objects that give the error? – gnud Mar 13 '19 at 01:06
  • The difference here is arrays. I don't attempt to handle arrays in a nice way, I just replace the whole array if there is a difference. It would not be hard to compare element by element, but imagine if you have an array of 700 elements, and you remove element 0. Detecting these kinds of changes are complicated. The naive way of comparing arrays, would end up "replacing" elements 0-698, and removing element 699. – gnud Mar 13 '19 at 14:07
  • it works perfectly when all the properties are object. But if a property is a array, the patch will replace the entire array instead, which is redundant and in some case affect the performance. – Wei J. Zheng Nov 06 '20 at 17:31
  • 2
    here is a updated version that supports replacing array element instead of the whole array https://gist.github.com/yww325/b71563462cb5b5f2ea29e0143634bebe – yww325 Dec 19 '20 at 04:08
0

You could use my DiffAnalyzer. It's based on reflection and you can configure the depth you want to analyze.

https://github.com/rcarubbi/Carubbi.DiffAnalyzer

var before = new User { Id = 1, Name="foo"};
var after= new User  { Id = 2, Name="bar"};
var analyzer = new DiffAnalyzer();
var results = analyzer.Compare(before, after);
rcarubbi
  • 123
  • 1
  • 10
-3

You can use this

You can install using NuGet, see SimpleHelpers.ObjectDiffPatch at NuGet.org

PM> Install-Package SimpleHelpers.ObjectDiffPatch

Use:

StyleDetail styleNew = new StyleDetail() { Id = "12", Code = "first" };
StyleDetail styleOld = new StyleDetail() { Id = "23", Code = "second" };
var diff = ObjectDiffPatch.GenerateDiff (styleOld , styleNew );

// original properties values
Console.WriteLine (diff.OldValues.ToString());

// updated properties values
Console.WriteLine (diff.NewValues.ToString());