Найти и вернуть различия JSON, используя newtonsoft в С#?
Я хотел бы получить список частей JSON, которые не совпадают при сравнении с Newtonsoft.
У меня есть этот код, который сравнивает:
JObject xpctJSON = JObject.Parse(expectedJSON);
JObject actJSON = JObject.Parse(actualJSON);
bool res = JToken.DeepEquals(xpctJSON, actJSON);
Но не может найти ничего, что возвращает diff.
Ответы
Ответ 1
Это относительно старый вопрос, но выставляя один из возможных способов решения этого вопроса, предполагая, что желаемый результат - это именно то, какие значения свойств изменены
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");
}
Примечание. Это не было проверено для очень глубокой иерархии.
Ответ 2
Вот рекурсивная версия, которую я написал. Вы вызываете CompareObjects с двумя JObjects и возвращаете список различий. Вы вызываете CompareArrays с двумя JArrays и сравниваете массивы. Массивы и объекты могут вставляться друг в друга.
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;
}
Ответ 3
Просто чтобы помочь в будущих запросах. Там хороший инструмент для сравнения json, с которым я столкнулся. Он работает безупречно для diff/patch json структур:
jsondiffpatch.net Там также есть пакет nuget.
использование простое.
var jdp = new JsonDiffPatch();
JToken diffResult = jdp.Diff(leftJson, rightJson);
Ответ 4
Обратите внимание на следующие библиотеки:
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;
}
ЗАМЕЧАНИЕ: если этот метод возвращает пустой IEnumerable, то ACTUAL JSON имеет все необходимые ключи в соответствии со структурой ожидаемого JSON.
ПРИМЕЧАНИЕ: у фактического JSON все еще может быть больше ключей, которые ожидаемый JSON не требуется.
чтобы объяснить мои заметки далее...
Предположим, что ваш ожидаемый JSON:
{ Id: 1, Name: "Item One", Value: "Sample" }
и что ваш ACTUAL JSON:
{ Id: 1, Name: "Item One", SomeProp: "x" }
указанная выше функция сообщит вам, что отсутствует ключ Value, но ничего не будет упоминать о кнопке SomeProp... если только вы не меняете параметры ввода.
Ответ 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;
}