在C#中使用newtonsoft查找并返回JSON差异?

use*_*908 44 c# json json.net

我想得到一个使用Newtonsoft进行比较时不匹配的JSON部分列表.

我有这个代码比较:

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

bool res = JToken.DeepEquals(xpctJSON, actJSON);
Run Code Online (Sandbox Code Playgroud)

但找不到任何可以返回差异的东西.

Ach*_*les 23

只是为了帮助未来的查询.我遇到了一个很好的json diff工具.它对于json结构的diff/patch非常完美:

jsondiffpatch.net 还有一个nuget包.

用法很简单.

var jdp = new JsonDiffPatch();
JToken diffResult = jdp.Diff(leftJson, rightJson);
Run Code Online (Sandbox Code Playgroud)

  • 这真的很好,但你不知道,在执行 JArrays diff 时有什么选项可以进行一些排序吗?还没有找到任何工具.. (2认同)

Wal*_*ter 18

这是我写的递归版本.您使用两个JObject调用CompareObjects,它返回差异列表.您使用两个JArrays调用CompareArrays并比较数组.数组和对象可以互相嵌套.

更新:@nttakr在下面的评论中指出,这种方法实际上是一种偏差算法.它只会告诉您与源列表的不同之处.如果源中不存在键但目标列表中存在该键,则将忽略该差异.这是我的测试要求的设计.这使您可以测试所需的项目,而无需在完成比较之前将其从目标中删除.

    /// <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;
    }
Run Code Online (Sandbox Code Playgroud)


Pra*_*vin 15

这是一个相对古老的问题,但是发布了解决此问题的可能方法之一,假设您想要的结果正是更改了哪些属性值

   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");
   }  
Run Code Online (Sandbox Code Playgroud)

注意:尚未针对非常深层次的层次结构进行测试.

  • 嗨@Walter,你能发布你的递归解决方案吗? (2认同)

小智 11

这几乎是一个古老的线程,但由于我几个月前来到这里寻找一个可靠的工具,但找不到一个,我已经编写了自己的工具,如果您正在寻找类似于以下内容的内容,您可以使用它:

JSON 1

{
  "name":"John",
  "age":30,
  "cars": {
    "car1":"Ford",
    "car2":"BMW",
    "car3":"Fiat"
  }
 }
Run Code Online (Sandbox Code Playgroud)

JSON 2

{
  "name":"John",
  "cars": {
    "car1":"Ford",
    "car2":"BMW",
    "car3":"Audi",
    "car4":"Jaguar"
  }
 }
Run Code Online (Sandbox Code Playgroud)

用法


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

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

Run Code Online (Sandbox Code Playgroud)

结果

{
  "-age": 30,
  "*cars": {
    "*car3": "Fiat",
    "+car4": "Jaguar"
  }
}
Run Code Online (Sandbox Code Playgroud)

请随意查看源代码并查看测试,欢迎您提供反馈:)

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


Was*_*emS 5

请注意以下库:

using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
Run Code Online (Sandbox Code Playgroud)

我不完全确定我是否正确理解了您的问题。我假设您正在尝试确定实际 JSON 中缺少哪些键。

如果您只是对缺少的密钥感兴趣,下面的代码将对您有所帮助,如果没有,请提供您尝试识别的差异类型的示例。

  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;
    }
Run Code Online (Sandbox Code Playgroud)

请注意,如果此方法返回空的 IEnumerable,则 ACTUAL JSON 具有根据预期 JSON 结构所需的所有键。

注意:实际的 JSON 仍然可能有更多预期的 JSON 不需要的键。

进一步解释我的笔记...

假设您预期的 JSON 是:

{ Id: 1, Name: "Item One", Value: "Sample" }
Run Code Online (Sandbox Code Playgroud)

并且您的实际 JSON 是:

{ Id: 1, Name: "Item One", SomeProp: "x" }
Run Code Online (Sandbox Code Playgroud)

上面的函数会告诉您 Value 键丢失,但不会提及 SomeProp 键的任何内容......除非您交换输入参数。


Dmi*_*kov 5

我的解决方案基于先前答案中的想法:

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)
                {
                    diff[k] = FindDiff(current[k], model[k]);
                }
            }
            break;
        case JTokenType.Array:
            {
                var current = Current as JArray;
                var model = Model as JArray;
                diff["+"] = new JArray(current.Except(model));
                diff["-"] = new JArray(model.Except(current));
            }
            break;
        default:
            diff["+"] = Current;
            diff["-"] = Model;
            break;
    }

    return diff;
}
Run Code Online (Sandbox Code Playgroud)