Подтвердить что ты не робот

Найти и вернуть различия JSON, используя newtonsoft в С#?

Я хотел бы получить список частей JSON, которые не совпадают при сравнении с Newtonsoft.

У меня есть этот код, который сравнивает:

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

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

Но не может найти ничего, что возвращает diff.

4b9b3361

Ответ 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;
}