80

I'd like to get a list of the JSON parts that don't match when doing a comparison using Newtonsoft.

I have this code that compares:

JObject xpctJSON = JObject.Parse(expectedJSON);
JObject actJSON = JObject.Parse(actualJSON);

bool res = JToken.DeepEquals(xpctJSON, actJSON);

But can't find anything that returns the diff's.

Ondrej Svejdar
  • 21,349
  • 5
  • 54
  • 89
user167908
  • 977
  • 2
  • 9
  • 11
  • This is a good question, and unfortunately I don't have a good answer, but this question and answer might help you: http://stackoverflow.com/questions/630263/c-compare-contents-of-two-ienumerables – Matthew Haugen Jul 21 '14 at 22:50
  • 3
    I don't think that your *demand* can be answered easily. For ex, what is the diff between `{a:{b:1,c:{d:3}}}` and `{a:{b:1,c:{d:4}}}`, only `d`? But now `c`s have different values. So they are also different. If `c`s are different then so `a`s. Instead of this, why don't you ask what you *really* want to do. – EZI Jul 21 '14 at 23:03
  • 1
    Also, the order of properties could be different in JSON but still represent equivalent objects, e.g. `{"a":"foo","b":"bar"}` vs. `{"b":"bar","a":"foo"}`. Would you want that to be considered as a difference or not? – Brian Rogers Jul 22 '14 at 17:06
  • I have this very same need, for logging difference of JSON packets. I am thinking that I'll reduce each key: value pair into a hash table. the key will be the name with dots all the way up the tree `parent.child.property` so that each is unique, then if you do that for each json graph you'd be able to use Linq to quickly compare and find the exceptions, and be able to report which ones are different. is that over thinking it? – Nateous May 04 '18 at 20:11

9 Answers9

46

Just to help future queries. There's a nice json diff tool I came across. It works flawlessly for diff/patch of json structures:

jsondiffpatch.net There's also a nuget package for it.

usage is straightforward.

var jdp = new JsonDiffPatch();
JToken diffResult = jdp.Diff(leftJson, rightJson);
Faheem
  • 1,423
  • 2
  • 13
  • 22
  • 2
    It's really nice, but don't you know, is there any option to make some sorting while performing JArrays diff ? Can't find any tool for that yet.. – tsul Dec 25 '17 at 10:21
  • @tsul I guess one option might be deserialize the Json to some structure, sort the arrays in that structure, then serialize it to a json and feed it to the diff. – Faheem Feb 21 '18 at 12:33
  • Thanks, I've found Json.Comparer for that since then, it can be supplied by custom sorter and property filters. Another option I've found is to deserialize to Xml, and then to use Microsoft.XmlDiffPatch. It even does not need any customization, just use XmlDiffOptions.IgnoreChildOrder. – tsul Feb 21 '18 at 15:28
  • Problem with this package is that the diff for object arrays is not great, when using the 'simple' option – Bassie Feb 15 '22 at 16:50
23

Here is a recursive version that I wrote. You call CompareObjects with two JObjects and it returns a list of the differences. You call CompareArrays with two JArrays and it compares the arrays. Arrays and Objects can be nested inside each other.

UPDATE: @nttakr points out in the comment below that this method is actually a partial difference algorithm. It only tells you about differences from the point of view of the source list. If a key doesn't exist in the source but does exist in the target list, that difference will be ignored. This is by design for my testing requirements. This allows you to test for just then items you want without having to delete them from the target before the comparisons are done.

    /// <summary>
    /// Deep compare two NewtonSoft JObjects. If they don't match, returns text diffs
    /// </summary>
    /// <param name="source">The expected results</param>
    /// <param name="target">The actual results</param>
    /// <returns>Text string</returns>

    private static StringBuilder CompareObjects(JObject source, JObject target)
    {
        StringBuilder returnString = new StringBuilder();
        foreach (KeyValuePair<string, JToken> sourcePair in source)
        {
            if (sourcePair.Value.Type == JTokenType.Object)
            {
                if (target.GetValue(sourcePair.Key) == null)
                {
                    returnString.Append("Key " + sourcePair.Key
                                        + " not found" + Environment.NewLine);
                }
                else if (target.GetValue(sourcePair.Key).Type != JTokenType.Object) {
                    returnString.Append("Key " + sourcePair.Key
                                        + " is not an object in target" + Environment.NewLine);
                }                    
                else
                {
                    returnString.Append(CompareObjects(sourcePair.Value.ToObject<JObject>(),
                        target.GetValue(sourcePair.Key).ToObject<JObject>()));
                }
            }
            else if (sourcePair.Value.Type == JTokenType.Array)
            {
                if (target.GetValue(sourcePair.Key) == null)
                {
                    returnString.Append("Key " + sourcePair.Key
                                        + " not found" + Environment.NewLine);
                }
                else
                {
                    returnString.Append(CompareArrays(sourcePair.Value.ToObject<JArray>(),
                        target.GetValue(sourcePair.Key).ToObject<JArray>(), sourcePair.Key));
                }
            }
            else
            {
                JToken expected = sourcePair.Value;
                var actual = target.SelectToken(sourcePair.Key);
                if (actual == null)
                {
                    returnString.Append("Key " + sourcePair.Key
                                        + " not found" + Environment.NewLine);
                }
                else
                {
                    if (!JToken.DeepEquals(expected, actual))
                    {
                        returnString.Append("Key " + sourcePair.Key + ": "
                                            + sourcePair.Value + " !=  "
                                            + target.Property(sourcePair.Key).Value
                                            + Environment.NewLine);
                    }
                }
            }
        }
        return returnString;
    }

    /// <summary>
    /// Deep compare two NewtonSoft JArrays. If they don't match, returns text diffs
    /// </summary>
    /// <param name="source">The expected results</param>
    /// <param name="target">The actual results</param>
    /// <param name="arrayName">The name of the array to use in the text diff</param>
    /// <returns>Text string</returns>

    private static StringBuilder CompareArrays(JArray source, JArray target, string arrayName = "")
    {
        var returnString = new StringBuilder();
        for (var index = 0; index < source.Count; index++)
        {

            var expected = source[index];
            if (expected.Type == JTokenType.Object)
            {
                var actual = (index >= target.Count) ? new JObject() : target[index];
                returnString.Append(CompareObjects(expected.ToObject<JObject>(),
                    actual.ToObject<JObject>()));
            }
            else
            {

                var actual = (index >= target.Count) ? "" : target[index];
                if (!JToken.DeepEquals(expected, actual))
                {
                    if (String.IsNullOrEmpty(arrayName))
                    {
                        returnString.Append("Index " + index + ": " + expected
                                            + " != " + actual + Environment.NewLine);
                    }
                    else
                    {
                        returnString.Append("Key " + arrayName
                                            + "[" + index + "]: " + expected
                                            + " != " + actual + Environment.NewLine);
                    }
                }
            }
        }
        return returnString;
    }
Walter
  • 498
  • 1
  • 5
  • 11
  • 1
    Your algorithm is not complete and is missing differences. If Target has "more" properties or "more" array items than source your algorithm will flag source and target as EQUAL but the are NOT. – nttakr Apr 19 '17 at 10:50
  • Yes, that is actually by design. The system under test sometimes has extra properties that we don't care about. This code allows me to check for the items that I care about and ignore the items that I don't by leaving them off the source list. In .NET/C#, extra items are usually not a problem. I've heard there are Java based system that blow up when this happens (but I'm not testing those. – Walter Apr 20 '17 at 00:43
  • 1
    I think there might be a bug in `CompareObjects` if the source is a Object but the target is a value. I think you might need an `else if (target.GetValue(sourcePair.Key).Type != JTokenType.Object)`. inside the `if (sourcePair.Value.Type == JTokenType.Object)` statement. Thank for the code snippet btw. It was really useful! – Jon F. May 02 '17 at 14:36
  • I think CompareArrays method might be improved to compare arrays of arrays. – Dmytro Laptin Aug 11 '17 at 08:40
  • Our data models don't have arrays of arrays. They either have arrays of primitives or arrys of objects (which could include arrays). It shoiuld be simple to include a second else if clause that recursively called CompareArrays to check the sub array. – Walter Aug 12 '17 at 22:57
22

There is my solution based on the ideas from the previous answers:

public static JObject FindDiff(this JToken Current, JToken Model)
{
    var diff = new JObject();
    if (JToken.DeepEquals(Current, Model)) return diff;

    switch(Current.Type)
    {
        case JTokenType.Object:
            {
                var current = Current as JObject;
                var model = Model as JObject;
                var addedKeys = current.Properties().Select(c => c.Name).Except(model.Properties().Select(c => c.Name));
                var removedKeys = model.Properties().Select(c => c.Name).Except(current.Properties().Select(c => c.Name));
                var unchangedKeys = current.Properties().Where(c => JToken.DeepEquals(c.Value, Model[c.Name])).Select(c => c.Name);
                foreach (var k in addedKeys)
                {
                    diff[k] = new JObject
                    {
                        ["+"] = Current[k]
                    };
                }
                foreach (var k in removedKeys)
                {
                    diff[k] = new JObject
                    {
                        ["-"] = Model[k]
                    };
                }
                var potentiallyModifiedKeys = current.Properties().Select(c => c.Name).Except(addedKeys).Except(unchangedKeys);
                foreach (var k in potentiallyModifiedKeys)
                {
                    var foundDiff = FindDiff(current[k], model[k]);
                    if(foundDiff.HasValues) diff[k] = foundDiff;
                }
            }
            break;
        case JTokenType.Array:
            {
                var current = Current as JArray;
                var model = Model as JArray;
                var plus = new JArray(current.Except(model, new JTokenEqualityComparer()));
                var minus = new JArray(model.Except(current, new JTokenEqualityComparer()));
                if (plus.HasValues) diff["+"] = plus;
                if (minus.HasValues) diff["-"] = minus;
            }
            break;
        default:
            diff["+"] = Current;
            diff["-"] = Model;
            break;
    }

    return diff;
}
Dzmitry Paliakou
  • 1,587
  • 19
  • 27
  • 1
    Your extension method is 10 times faster than JsonDiffPatch.net, if used with the example jsons from @Pravin solution. – GreenEyedAndy Nov 08 '19 at 09:37
  • 1
    Modified to use the JTokenEqualityComparer for array comparison. Without it I was seeing array differences represented as full array element replacement. – BrianS Mar 13 '20 at 20:26
  • This is really slick. I adapted it to write variances to a POCO that included the object's path, and it is a perfect format for a drift report I'm working on. Thank you for this! – Mike Loux Jun 03 '20 at 21:59
  • Only way I would be happier is if this was a built in method. Well done. – b_levitt Jul 14 '20 at 01:17
  • 1
    It's worth noting that in the case of properties that are arrays of objects, it gives the entire object even with a single property level difference on that object. I'm certain that is to keep it generic. Doing more requires aligning the arrays by an identifier property or if you really wanted to go crazy creating a diff/match to do it generically. – b_levitt Jul 14 '20 at 17:39
  • 1
    @BrianS Do you have an example of how you solved the comparison between two arrays ? – Ayoub Salhi Jul 29 '20 at 16:03
  • @AyoubSalhi the answer was updated with my suggested changes (which was to [compare with the JTokenEqualityComparer](https://stackoverflow.com/revisions/53654737/2)). The current code sample should be able to display array diffs more succinctly. – BrianS Aug 08 '20 at 01:03
17

This is relatively old question but posting one of the possible ways to solve this, assuming that the result you want is exactly which property values are changed

   string sourceJsonString = "{'name':'John Doe','age':'25','hitcount':34}";
   string targetJsonString = "{'name':'John Doe','age':'26','hitcount':30}";

   JObject sourceJObject = JsonConvert.DeserializeObject<JObject>(sourceJsonString);
   JObject targetJObject = JsonConvert.DeserializeObject<JObject>(targetJsonString);

   if (!JToken.DeepEquals(sourceJObject, targetJObject))
   {
     foreach (KeyValuePair<string, JToken> sourceProperty in sourceJObject)
     {
         JProperty targetProp = targetJObject.Property(sourceProperty.Key);

          if (!JToken.DeepEquals(sourceProperty.Value, targetProp.Value))
          {
              Console.WriteLine(string.Format("{0} property value is changed", sourceProperty.Key));
          }
          else
          {
              Console.WriteLine(string.Format("{0} property value didn't change", sourceProperty.Key));
          }
      }
   }
   else
   {
      Console.WriteLine("Objects are same");
   }  

Note: This has not been tested for very deep hierarchy.

Pravin
  • 650
  • 1
  • 7
  • 17
  • Thanks, I converted this to a recursive function and used it do compare objects and sub objects all the way down to the base types. – Walter Feb 03 '16 at 18:17
  • 2
    Hi @Walter, would you please post your recursive solution? – bboyle1234 Sep 19 '16 at 03:40
  • 1
    Code doesn't fit well in comments, so it has been added as an answer. @bboyle1234 please upvote the answer if you find it useful. – Walter Sep 21 '16 at 18:15
9

This is pretty much and old thread, but since I came here a few month back to look for a reliable tool and couldn't find one, I have written my own, you may use it if you are looking something similar to the following:

JSON 1

{
  "name":"John",
  "age":30,
  "cars": {
    "car1":"Ford",
    "car2":"BMW",
    "car3":"Fiat"
  }
 }

JSON 2

{
  "name":"John",
  "cars": {
    "car1":"Ford",
    "car2":"BMW",
    "car3":"Audi",
    "car4":"Jaguar"
  }
 }

Usage


 var j1 = JToken.Parse(Read(json1));
 var j2 = JToken.Parse(Read(json2));

 var diff = JsonDifferentiator.Differentiate(j1,j2);

Result

{
  "-age": 30,
  "*cars": {
    "*car3": "Fiat",
    "+car4": "Jaguar"
  }
}

Feel free to checkout the source code and look at the tests, your feedbacks are welcome :)

https://www.nuget.org/packages/JsonDiffer

Amin M
  • 113
  • 1
  • 8
5

NOTE the following libraries:

using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

I'm not entirely sure I understand your question correctly. I am assuming you are trying to identify what keys are missing from the actual JSON.

If you are just interested in the missing KEYS the below code will help you, if not, please provide an example of the types of differences you are trying to identify.

  public IEnumerable<JProperty> DoCompare(string expectedJSON, string actualJSON)
    {
        // convert JSON to object
        JObject xptJson = JObject.Parse(expectedJSON);
        JObject actualJson = JObject.Parse(actualJSON);

        // read properties
        var xptProps = xptJson.Properties().ToList();
        var actProps = actualJson.Properties().ToList();

        // find missing properties
        var missingProps = xptProps.Where(expected => actProps.Where(actual => actual.Name == expected.Name).Count() == 0);

        return missingProps;
    }

NOTE that if the this method returns an empty IEnumerable then the ACTUAL JSON has all the keys required according to the structure of the expected JSON.

NOTE: the actual JSON could still have more keys that the expected JSON does not required.

to explain my notes further...

assume your expected JSON is:

{ Id: 1, Name: "Item One", Value: "Sample" }

and that your ACTUAL JSON is :

{ Id: 1, Name: "Item One", SomeProp: "x" }

the above function will tell you that the Value key is missing, but will not mention anything about the SomeProp key...unless your swap the input parameters around.

WaseemS
  • 149
  • 2
  • 7
  • The algorithm is incorrect. It only checks if actualJSON contains all properties of expectedJSON. What about properties in actualJSON that are not present in expectedJSON? This is also a "difference" but your code will not find it. Also changed values of a property also qualify as a difference. Your code will completely ignore that. – nttakr Apr 19 '17 at 10:56
3

I have converted to be more accurate in array of objects

public static JObject FindDiff(this JToken leftJson, JToken rightJson)
{
    var difference = new JObject();
    if (JToken.DeepEquals(leftJson, rightJson)) return difference;

    switch (leftJson.Type) {
        case JTokenType.Object:
            {
                var LeftJSON = leftJson as JObject;
                var RightJSON = rightJson as JObject;
                var RemovedTags = LeftJSON.Properties().Select(c => c.Name).Except(RightJSON.Properties().Select(c => c.Name));
                var AddedTags = RightJSON.Properties().Select(c => c.Name).Except(LeftJSON.Properties().Select(c => c.Name));
                var UnchangedTags = LeftJSON.Properties().Where(c => JToken.DeepEquals(c.Value, RightJSON[c.Name])).Select(c => c.Name);
                foreach(var tag in RemovedTags)
                {
                    difference[tag] = new JObject
                    {
                        ["-"] = LeftJSON[tag]
                    };
                }
                foreach(var tag in AddedTags)
                {
                    difference[tag] = new JObject
                    {
                        ["-"] = RightJSON[tag]
                    };
                }
                var ModifiedTags = LeftJSON.Properties().Select(c => c.Name).Except(AddedTags).Except(UnchangedTags);
                foreach(var tag in ModifiedTags)
                {
                    var foundDifference = Compare(LeftJSON[tag], RightJSON[tag]);
                    if (foundDifference.HasValues) {
                        difference[tag] = foundDifference;
                    }
                }
            }
            break;
        case JTokenType.Array:
            {
                var LeftArray = leftJson as JArray;
                var RightArray = rightJson as JArray;

                if (LeftArray != null && RightArray != null) {
                    if (LeftArray.Count() == RightArray.Count()) {
                        for (int index = 0; index < LeftArray.Count(); index++)
                        {
                            var foundDifference = Compare(LeftArray[index], RightArray[index]);
                            if (foundDifference.HasValues) {
                                difference[$"{index}"] = foundDifference;
                            }
                        }
                    }
                    else {
                        var left = new JArray(LeftArray.Except(RightArray, new JTokenEqualityComparer()));
                        var right = new JArray(RightArray.Except(LeftArray, new JTokenEqualityComparer()));
                        if (left.HasValues) {
                            difference["-"] = left;
                        }
                        if (right.HasValues) {
                            difference["+"] = right;
                        }
                    }
                }
            }
            break;
        default:
            difference["-"] = leftJson;
            difference["+"] = rightJson;
            break;
    }

    return difference;
}
0

None of the answers here were quite what I needed.

Here is a method that returns a JObject for each of the two objects compared. The JObjects contain only properties that differ. This is good for scanning changes to an entity and storing a before and after snapshot (serialize the JObjects).

NB: The change scanning occurs only on top level properties.

        private Tuple<JObject, JObject> GetDeltaState<TRead>(TRead before, TRead after)
    {
        if (before == null && after == null)
            return new Tuple<JObject, JObject>(null, null);

        JObject beforeResult;
        JObject afterResult;

        // If one record is null then we don't need to scan for changes
        if (before == null ^ after == null)
        {
            beforeResult = before == null ? null : JObject.FromObject(before, _jsonSerializer);
            afterResult = after == null ? null : JObject.FromObject(after, _jsonSerializer);

            return new Tuple<JObject, JObject>(beforeResult, afterResult);
        }

        beforeResult = new JObject();
        afterResult = new JObject();

        JObject beforeState = JObject.FromObject(before, _jsonSerializer);
        JObject afterState = JObject.FromObject(after, _jsonSerializer);

        // Get unique properties from each object
        IEnumerable<JProperty> properties = beforeState.Properties().Concat(afterState.Properties()).DistinctBy(x => x.Name);

        foreach (JProperty prop in properties)
        {
            JToken beforeValue = beforeState[prop.Name];
            JToken afterValue = afterState[prop.Name];

            if (JToken.DeepEquals(beforeValue, afterValue))
                continue;

            beforeResult.Add(prop.Name, beforeValue);
            afterResult.Add(prop.Name, afterValue);
        }

        return new Tuple<JObject, JObject>(beforeResult, afterResult);
    }
Ste Brown
  • 109
  • 1
  • 3
-1
    public static void Validate(JToken actual, JToken expected, IList<string> diffMessages)
    {
        if (actual == null && expected == null)
        {
            // handle accroding to requirement
            return;
        }

        if (actual == null)
        {
            diffMessages.Add($"Diff on {expected.Path}: actual - null, expected - {expected}");
            return;
        }

        if (expected == null)
        {
            diffMessages.Add($"Diff on {actual.Path}: actual - {actual}, expected - null");
            return;
        }

        if (actual.Type != JTokenType.Object && actual.Type != JTokenType.Array && actual.Type != JTokenType.Property)
        {
            if (!JToken.DeepEquals(actual, expected))
            {
                diffMessages.Add($"Diff on {actual.Path}: actual- {actual}, expected - {expected}");
            }

            return;
        }


        // recursion

        foreach (var jItem in actual)
        {
            var newExpected = expected.Root.SelectToken(jItem.Path);
            Validate(jItem, newExpected, diffMessages);
        }

    }
  • 3
    While this code may solve the question, [including an explanation](https://meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please edit your answer to add explanations and give an indication of what limitations and assumptions apply. – David Buck Mar 25 '20 at 14:48