【问题标题】:How to compare two Json objects using C#如何使用 C# 比较两个 Json 对象
【发布时间】:2019-03-09 18:43:03
【问题描述】:

我有两个 Json 对象如下需要比较。我正在使用 Newtonsoft 库进行 Json 解析。

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

我正在使用 Fluent Assertions 来比较它。但问题是 Fluent 断言仅在属性计数/名称不匹配时才会失败。如果 json 值不同,则通过。当价值观不同时,我需要失败。

InstanceObjActual.Should().BeEquivalentTo(InstanceObjExpected);

例如,我将实际和预期的 json 进行比较,如下所示。而使用上面的比较方式使它们通过这是错误的。

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

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

【问题讨论】:

  • 为什么不比较字符串而不是比较json对象呢?
  • 不能进行简单的字符串比较吗? jsonExpected == jsonActual.
  • JSON 实际上不是一个字符串,所以上面的 cmets 是无关紧要的。 { "id": "5" } 应该与 { "id" : "5" } 相同。所以你不能使用字符串比较器来比较 JSON。
  • ...除非 JSON 总是由相同的过程创建并且项目是有序的。
  • @JessedeWit ...我不提倡字符串比较,因为它很讨厌,但是,如果通过保证属性排序的序列化程序往返,它可能会起作用。

标签: c# unit-testing json.net assertion fluent-assertions


【解决方案1】:

我做了更多的挖掘,并能够找出为什么 OP 的测试代码没有按预期运行。我能够通过安装和使用FluentAssertions.Json nuget 包来修复它。

一件重要的事情:

一定要包含using FluentAssertions.Json 否则为假 可能会出现积极的情况。

测试代码如下:

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);
    }
}

运行测试:

【讨论】:

  • 是的。仅包括 FluentAssertions.Json 对我来说效果很好。我想知道没有它会有多糟糕。谢谢!
【解决方案2】:

考虑使用 Newtonsoft 提供的JToken.DeepEquals() 方法。无论您使用的是哪种测试框架,它看起来都会像这样:

Console.WriteLine(JToken.DeepEquals(InstanceObjActual, InstanceObjExpected));
// false

【讨论】:

  • 比我快 10 秒 :) 这是正确的答案。
  • 好东西,这比我的解决方案更简单、更清洁。
  • 我发现在单元测试中使用JToken.DeepEquals 的唯一问题是无法找到 json 字符串中的差异。将 json 反序列化为 C# 对象并比较它们会提供更好的错误消息(请参阅我附在答案中的屏幕截图)。
  • @RuiJarimba 当然,反序列化为 C# 对象会提供更好的错误消息,但我没有在所有地方都使用这种方法,因为它是一个测试项目,我们处理各种 Json 响应和创建类的工作每个对象都非常高且过载过多。
  • @Nandakumar1712 很公平。检查我的答案,其中一个正是您需要的,如果测试失败,它会给您一个不错的错误消息。
【解决方案3】:

制作了一个非递归方法来移除双胞胎 - 想法是从非常相似的 JSON 中移除相同的元素,以便每个对象中只保留不同的节点:

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);
}

我的 GitHub 上的完整代码 + 解析器(个人资料或以下)。
旧的 CSV 版本也对我的问题How to compare big JSON's? 中提到的输入进行排序(新版本没有,因此当其中一个对象的顺序相反时可能会非常慢 - 在解析期间排序或至少比较两个邻居会更容易双胞胎作为搜索的第一步)

【讨论】:

  • 还有排序方法 => 也可以处理不同顺序的 JSON。
【解决方案4】:

一种选择是将 json 字符串反序列化为 C# 对象并进行比较。

与使用JToken.DeepEquals(如@JessedeWit 所建议)相比,这种方法需要更多的工作,但如果您的测试失败,它的优点是可以提供更好的错误消息(见下面的屏幕截图)。

您的 json 字符串可以建模为以下类:

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

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

在您的测试中,将 json 字符串反序列化为对象并进行比较:

[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:我在测试方法中使用了 NUnit 和 FluentAssertions。运行测试:

【讨论】:

  • 正如你所说的那样,它需要做更多的工作或更糟的是会在共享库周围产生很多耦合。
  • @user1496062 你对我的回答投了反对票吗?相反,仅仅因为涉及更多工作并不意味着这是一个糟糕的答案。正如我已经提到的,这种方法的优点是您将获得更有意义的错误消息。
  • 它是关于将 json 转换为对象而不是增加耦合而不是额外的工作。我们使用的大多数集成/端到端测试都没有这些对象,因此答案没有帮助,可能会导致其他人产生更多的耦合/问题。
  • @user1496062 这对您没有帮助,可能对其他人有用 - 这就是我想说的。
【解决方案5】:

将json反序列化为C#对象后,正确的方法是在反序列化的类中实现IComparable接口并比较2个对象。

所以:

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;
    }
}

【讨论】:

  • 实现 IEquatable 并覆盖 EqualsGetHashCode 会更有意义,因为没有好的方法可以判断其中哪个更大,只有它们相等或不相等。跨度>
  • 你是对的。忘记了 IEquatable 退出。我 == too_old。
猜你喜欢
  • 2016-09-05
  • 1970-01-01
  • 1970-01-01
  • 2017-11-29
  • 2014-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-03
相关资源
最近更新 更多