29

I have two Json objects as below need to be compared. I am using Newtonsoft libraries for Json parsing.

string InstanceExpected = jsonExpected;
string InstanceActual = jsonActual;
var InstanceObjExpected = JObject.Parse(InstanceExpected);
var InstanceObjActual = JObject.Parse(InstanceActual);

And I am using Fluent Assertions to compare it. But the problem is Fluent assertion fails only when the attribute count/names are not matching. If the json values are different it passes. I require to fail when values are different.

InstanceObjActual.Should().BeEquivalentTo(InstanceObjExpected);

For example I have the actual and expected json to compare as below. And using the above way of comparing make them Pass which is wrong.

{
  "Name": "20181004164456",
  "objectId": "4ea9b00b-d601-44af-a990-3034af18fdb1%>"  
}

{
  "Name": "AAAAAAAAAAAA",
  "objectId": "4ea9b00b-d601-44af-a990-3034af18fdb1%>"  
}
Liam
  • 27,717
  • 28
  • 128
  • 190
Nandakumar1712
  • 357
  • 1
  • 4
  • 11
  • 2
    Why don't you compare the strings instead of comparing the json objects? – Rui Jarimba Oct 04 '18 at 11:33
  • 1
    Wouldn't a simple string comparison work? `jsonExpected == jsonActual`. – Olivier Jacot-Descombes Oct 04 '18 at 11:33
  • The other option is to deserialize the json strings into C# objects and compare them. – Rui Jarimba Oct 04 '18 at 11:33
  • JSON is a string. So is it equal is as simple as `str1 == str2`. Given your examples that **will work**. If your saying it doesn't then you need to be clear why it doesn't – Liam Oct 04 '18 at 11:51
  • 13
    JSON is actually NOT a string, so above comments are irrelevant. `{ "id": "5" }` should be the same as `{ "id" : "5" }`. So you cannot use a string comparer to compare JSON. – Jesse de Wit Oct 04 '18 at 12:01
  • 3
    ... unless the JSON is always created by the same procedure and the items are ordered. – Olivier Jacot-Descombes Oct 04 '18 at 12:05
  • 2
    @JessedeWit ...I'm not advocating the string compare because it's nasty, however, if round-tripped through a serializer that makes guarantees about ordering of properties, it would probably work. – spender Oct 04 '18 at 12:06
  • @JessedeWit comparing strings is an option if the json strings are generated the same way, e.g. using JsonConvert class with the same serialization settings. – Rui Jarimba Oct 04 '18 at 12:18
  • Yes, ofcourse it is an option and if you are consistent it will work. But in my experience this will eventually lead to hard to solve bugs in your code, because the next developer will not know about the string comparison. Interpreting JSON as JSON is always safe and not harder to code or to understand. In fact, I usually find it easier to read code like this. Same goes for XML ofcourse. – Jesse de Wit Oct 04 '18 at 12:25
  • I personally don't like comparing the strings since I found it more error prone. – Nandakumar1712 Oct 05 '18 at 08:55

7 Answers7

41

I did a bit more digging and was able to find out why the OP's test code doesn't run as expected. I was able to fix it by installing and using the FluentAssertions.Json nuget package.

One important thing:

Be sure to include using FluentAssertions.Json otherwise false positives may occur.

Test code is the following:

using FluentAssertions;
using FluentAssertions.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;

[TestFixture]
public class JsonTests
{
    [Test]
    public void JsonObject_ShouldBeEqualAsExpected()
    {
        JToken expected = JToken.Parse(@"{ ""Name"": ""20181004164456"", ""objectId"": ""4ea9b00b-d601-44af-a990-3034af18fdb1%>"" }");
        JToken actual = JToken.Parse(@"{ ""Name"": ""AAAAAAAAAAAA"", ""objectId"": ""4ea9b00b-d601-44af-a990-3034af18fdb1%>"" }");

        actual.Should().BeEquivalentTo(expected);
    }
}

Running the test:

Unit test results

Rui Jarimba
  • 11,166
  • 11
  • 56
  • 86
36

Consider using the JToken.DeepEquals() method provided by Newtonsoft. It would look somewhat like this, regardless of which testing framework you're using:

Console.WriteLine(JToken.DeepEquals(InstanceObjActual, InstanceObjExpected));
// false
Jesse de Wit
  • 3,867
  • 1
  • 20
  • 41
  • 2
    Beat me to it by about 10 seconds :) This is the right answer. – spender Oct 04 '18 at 12:08
  • Good stuff, this is much simpler and cleaner solution than mine. – Rui Jarimba Oct 04 '18 at 12:21
  • 3
    The only problem I find using `JToken.DeepEquals` in unit tests is that there is no way to find what are the differences in the json strings. Deserializing the json into C# objects and comparing them gives much better error messages (see the screenshot I attached to my answer). – Rui Jarimba Oct 04 '18 at 12:28
  • 2
    @RuiJarimba Of course deserializing as C# objects gives better error messages, but I didnot use this approach in all the places since it's a test project and we deal with variety of Json responses and the effort for creating class object for each one is pretty high and too much of overload. – Nandakumar1712 Oct 05 '18 at 09:00
  • @Nandakumar1712 fair enough. Check my answers, one of them has exactly what you need, and it will give you a decent error message in case of test failure. – Rui Jarimba Oct 05 '18 at 09:15
  • I tried JToken.DeepEquals and it returns false positive – Mali Tbt Jan 13 '21 at 10:29
4

Made a non-recursive method which will remove twins - idea is to remove same elements from very similar JSONs, so that there will remain only different nodes in each object:

public void RemoveTwins(ref BreadthFirst bf1, ref BreadthFirst bf2) {
    JsonNode traversal = bf1.Next();
    Boolean removed = false;
    do {
        if (!removed) {
            if (bf2.Current != null) while (bf1.Level == bf2.Level && bf2.Next() != null) ;
            if (bf2.Current != null) while (bf1.Level != bf2.Level && bf2.Next() != null) ;
            else bf2.Current = bf2.root;
        }
        else traversal = bf1.Next();
        if (bf2.Level < 0) bf2.Current = bf2.Root;
        do {
            removed = bf1.NextAs(bf1.src, bf2, bf2.src);
            if (removed && bf1.Orphan && bf2.Orphan) {
                JsonNode same = bf1.Current.Parent;
                traversal = bf1.RemoveCurrent();
                same = bf2.Current.Parent;
                bf2.RemoveCurrent();
                bf1.UpdateLevel();
                bf2.UpdateLevel();
                if (traversal == null
                || bf1.Root == null || bf2.Root == null
                || (bf1.Level == 0 && bf1.Current.NodeBelow == null)) {
                    traversal = null;
                    break;
                }
            } else
            if (!removed) {
                break; 
            } else removed = false;
        } while (removed);
        if (!removed) traversal = bf1.Next();
    } while (traversal != null);
}

Complete code + parser on my GitHub (profile or below).
Older CSV version which also sorts input mentioned in my question here How to compare big JSON's? (new one does not, so it could be very slow when one of objects has reversed order - it would be easier to sort during parsing or at least compare both neighbours of twins as first search step)

Community
  • 1
  • 1
Jan
  • 2,178
  • 3
  • 14
  • 26
  • There is also sort method => JSONs with different order of items can be processed too. – Jan Jun 19 '19 at 13:00
2

One option is to deserialize the json strings into C# objects and compare them.

This approach requires more work comparing to using JToken.DeepEquals (as suggested by @JessedeWit), but has the advantage of giving better error messages if your tests fail (see screenshot below).

Your json string can be modelled into the following class:

public class Entity
{
    [JsonProperty("Name")]
    public string Name { get; set; }

    [JsonProperty("objectId")]
    public string ObjectId { get; set; }
}

In your test, deserialize the json strings into objects and compare them:

[TestFixture]
public class JsonTests
{
    [Test]
    public void JsonString_ShouldBeEqualAsExpected()
    {
        string jsonExpected = @"{ ""Name"": ""20181004164456"", ""objectId"": ""4ea9b00b-d601-44af-a990-3034af18fdb1%>"" }";
        string jsonActual = @"{ ""Name"": ""AAAAAAAAAAAA"", ""objectId"": ""4ea9b00b-d601-44af-a990-3034af18fdb1%>"" }";

        Entity expectedObject = JsonConvert.DeserializeObject<Entity>(jsonExpected);
        Entity actualObject = JsonConvert.DeserializeObject<Entity>(jsonActual);

        actualObject.Should().BeEquivalentTo(expectedObject);
    }
}

PS: I used NUnit and FluentAssertions in my test method. Running the test:

Unit test results

Rui Jarimba
  • 11,166
  • 11
  • 56
  • 86
  • As you say its a lot more work or worse can get a lot of coupling around shared libs. – user1496062 Feb 02 '20 at 23:57
  • 1
    @user1496062 did you downvote my answer? Just because there's more work involved it doesn't mean it's a bad answer, by the contrary. As I already mentioned, the advantage of this approach is that you'll have more meaningful error messages. – Rui Jarimba Feb 05 '20 at 15:53
  • Its about casting json to objects rather which increases coupling rather than extra work. Most integration / end to end test we use dont have these objects so the answer is not helpful and may lead others to more coupling / issues. – user1496062 Feb 18 '20 at 23:49
  • 1
    @user1496062 it's not helpful to you, it might be to someone else - that's what I was trying to say. – Rui Jarimba Feb 20 '20 at 07:47
0

you can try this:

    using Newtonsoft.Json.Linq;
    using System;
    using System.Collections.Generic;
    using System.Linq;

    namespace test
    {
        public static class ExtensionMethod
        {
            public static JObject FindDiff(this JToken actual, JToken expected, ref List<string> changedItems)
            {
                var diff = new JObject();
                string valueToPrint = string.Empty;
                if (JToken.DeepEquals(actual, expected)) return diff;
                else if (actual.Type != expected.Type)
                {
                    return diff;
                }

                switch (actual.Type)
                {
                    case JTokenType.Object:
                        {
                            var Initial = actual as JObject;
                            var Updated = expected as JObject;
                            var addedKeys = Initial.Properties().Select(c => c.Name).Except(Updated.Properties().Select(c => c.Name));
                            var removedKeys = Updated.Properties().Select(c => c.Name).Except(Initial.Properties().Select(c => c.Name));
                            var unchangedKeys = Initial.Properties().Where(c => JToken.DeepEquals(c.Value, expected[c.Name])).Select(c => c.Name);
                            foreach (var k in addedKeys)
                            {
                                diff[k] = new JObject
                                {
                                    ["+"] = actual[k]
                                };
                            }
                            foreach (var k in removedKeys)
                            {
                                diff[k] = new JObject
                                {
                                    ["-"] = expected[k]
                                };
                            }
                            var potentiallyModifiedKeys = Initial.Properties().Select(c => c.Name).Except(addedKeys).Except(unchangedKeys);
                            foreach (var k in potentiallyModifiedKeys)
                            {
                                var foundDiff = FindDiff(Initial[k], Updated[k], ref changedItems);
                                if (foundDiff == null)
                                    return foundDiff;

                                if (foundDiff.HasValues && (foundDiff["+"] != null || foundDiff["-"] != null))
                                {
                                    //Execute when json element is an String
                                    if (IsValueChanged(foundDiff))
                                    {
                                        changedItems.Add($"actual value '{foundDiff["+"].ToString()}' is not equal to expected value '{foundDiff["-"].ToString()}'");
                                    }
                                    //Execute when json element is an Array
                                    else
                                    {
                                        for (int i = 0; i < foundDiff["+"].Count(); i++)
                                        {
                                            var foundDiffOfArray = FindDiff(foundDiff["+"][i], foundDiff["-"][i], ref changedItems);
                                            if (IsValueChanged(foundDiffOfArray))
                                            {
                                                changedItems.Add($"actual value '{foundDiff["+"].ToString()}' is not equal to expected value '{foundDiff["-"].ToString()}'");
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        break;
                    //"+" indicate the Original Value
                    //"-" indicate the Updated/Modified Value
                    case JTokenType.Array:
                        {
                            var current = actual as JArray;
                            var model = expected as JArray;
                            var plus = new JArray(current.ExceptAll(model, new JTokenEqualityComparer()));
                            var minus = new JArray(model.ExceptAll(current, new JTokenEqualityComparer()));
                            if (plus.HasValues) diff["+"] = plus;
                            if (minus.HasValues) diff["-"] = minus;
                        }
                        break;
                    default:
                        diff["+"] = actual;
                        diff["-"] = expected;
                        break;
                }

                return diff;
            }

            public static bool IsValueChanged(JObject foundDiff)
            {
                return (foundDiff["-"] != null && foundDiff["-"].Type == JTokenType.String)
                    || (foundDiff["+"] != null && foundDiff["+"].Type == JTokenType.String);
            }

            public static IEnumerable<TSource> ExceptAll<TSource>(
            this IEnumerable<TSource> first,
            IEnumerable<TSource> second)
            {
                return ExceptAll(first, second, null);
            }

            public static IEnumerable<TSource> ExceptAll<TSource>(
                this IEnumerable<TSource> first,
                IEnumerable<TSource> second,
                IEqualityComparer<TSource> comparer)
            {
                if (first == null) { throw new ArgumentNullException("first"); }
                if (second == null) { throw new ArgumentNullException("second"); }


                var secondCounts = new Dictionary<TSource, int>(comparer ?? EqualityComparer<TSource>.Default);
                int count;
                int nullCount = 0;

                // Count the values from second
                foreach (var item in second)
                {
                    if (item == null)
                    {
                        nullCount++;
                    }
                    else
                    {
                        if (secondCounts.TryGetValue(item, out count))
                        {
                            secondCounts[item] = count + 1;
                        }
                        else
                        {
                            secondCounts.Add(item, 1);
                        }
                    }
                }

                // Yield the values from first
                foreach (var item in first)
                {
                    if (item == null)
                    {
                        nullCount--;
                        if (nullCount < 0)
                        {
                            yield return item;
                        }
                    }
                    else
                    {
                        if (secondCounts.TryGetValue(item, out count))
                        {
                            if (count == 0)
                            {
                                secondCounts.Remove(item);
                                yield return item;
                            }
                            else
                            {
                                secondCounts[item] = count - 1;
                            }
                        }
                        else
                        {
                            yield return item;
                        }
                    }
                }
            }
        }
    }
-1

After you deserialize the json to C# object the correct way is to implement the IComparable interface in the deserialized class and compare the 2 objects.

So:

using System;
using System.Collections.Generic;

class MyObj : IComparable<MyObj>
{
    public string Name { get; set; }
    public string ObjectID { get; set; }

    public int CompareTo(MyObj other)
    {
        if ((this.Name.CompareTo(other.Name) == 0) &&
            (this.ObjectID.CompareTo(other.ObjectID) == 0))
        {
            return 0;
        }
        return -1;
    }
}
Itamar Kerbel
  • 2,508
  • 1
  • 22
  • 29
  • 2
    It would make more sense to implement `IEquatable` and override `Equals` and `GetHashCode` since there's no good way to say which of these is larger, only if they are equal or not. – juharr Oct 04 '18 at 11:42
  • You are right. Forgot that IEquatable exits. me == too_old. – Itamar Kerbel Oct 04 '18 at 11:54
-1

I needed two objects for Audit Logging and I wrote a code like below. It worked great for me.

https://github.com/wbish/jsondiffpatch.net

public static bool DeepCompare(this object obj, object another)
    {   
            var diffObj = new JsonDiffPatch();
        
            if (ReferenceEquals(obj, another)) return true;
            if ((obj == null) || (another == null)) return false;
            if (obj.GetType() != another.GetType()) return false;

            var objJson = JsonConvert.SerializeObject(obj);
            var anotherJson = JsonConvert.SerializeObject(another);
            var result = diffObj.Diff(objJson, anotherJson);
            return result == null;
    }
Aykut ÇALIŞKAN
  • 408
  • 4
  • 12