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

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

我有一个代码可以比较:

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


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

但是找不到任何返回差异的东西。

75831 次浏览

注意以下图书馆:

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

我不太明白你的问题是什么意思。 我假设您正在试图确定实际 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;
}

注意,如果这个方法返回一个空的 IEnumable,那么 ACTUAL JSON 将根据所期望的 JSON 的结构拥有所需的所有键。

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

进一步解释我的笔记。

假设您期望的 JSON 是:

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

你真正的 JSON 是:

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

上面的函数将告诉您 Value 键丢失了,但是不会提到任何关于 somProp 键的内容... ... 除非您将输入参数进行交换。

这是一个相对较老的问题,但是提出了一种可能的解决方法,假设您想要的结果恰好是更改了哪个属性值

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

注意: 这还没有经过非常深层次结构的测试。

这是我写的一个递归版本。您使用两个 JObjects 调用 CompareObjects,它会返回一个差异列表。您使用两个 JArray 调用 CompareArray,它会比较这些数组。数组和对象可以相互嵌套。

UPDATE :@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;
}

只是为了帮助未来的查询。我发现了一个不错的 json diff 工具。它在 diff/patch 的 json 结构上完美无缺:

Jsondiffpatch.net 这里还有一个巧克力套餐。

用法很简单。

var jdp = new JsonDiffPatch();
JToken diffResult = jdp.Diff(leftJson, rightJson);

我的解决方案基于前面的答案:

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


}

这是一个非常古老的线程,但是因为我几个月前来这里寻找一个可靠的工具,但是没有找到,所以我自己写了一个,如果你正在寻找类似的东西,你可以使用它:

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

用法


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


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


结果

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

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

Https://www.nuget.org/packages/jsondiffer

这里所有的答案都不是我所需要的。

下面是一个方法,它为所比较的两个对象中的每一个返回一个 JObject。JObjects 只包含不同的属性。这有利于扫描对实体的更改并存储快照之前和之后(序列化 JObjects)。

注意: 更改扫描只发生在顶级属性上。

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

我已经转换为更准确的对象数组

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