web-dev-qa-db-fra.com

Rechercher et renvoyer des différences JSON à l'aide de newtonsoft en C #?

J'aimerais obtenir une liste des composants JSON qui ne correspondent pas lors d'une comparaison avec Newtonsoft.

J'ai ce code qui compare:

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

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

Mais je ne trouve rien qui retourne les diff.

28
user167908

Juste pour aider les futures requêtes. Il y a un outil de diff json Nice que j'ai rencontré Cela fonctionne parfaitement pour diff/patch de structures json:

jsondiffpatch.net .__ Il existe aussi un paquet nuget pour cela. 

l'utilisation est simple.

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

Voici une version récursive que j'ai écrite. Vous appelez CompareObjects avec deux JObjects et une liste des différences est renvoyée. Vous appelez CompareArrays avec deux JArrays et il compare les tableaux. Les tableaux et les objets peuvent être imbriqués les uns dans les autres.

UPDATE: @nttakr indique dans le commentaire ci-dessous que cette méthode est en réalité un algorithme de différence partielle. Il vous indique uniquement les différences du point de vue de la liste source. Si une clé n'existe pas dans la source mais dans la liste des cibles, cette différence sera ignorée. C'est par conception pour mes exigences de test. Cela vous permet de tester les éléments de votre choix sans avoir à les supprimer de la cible avant la fin des comparaisons.

    /// <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;
    }
12
Walter

C'est une question relativement ancienne, mais il est utile de poster l'une des solutions possibles, en supposant que le résultat souhaité correspond exactement aux valeurs de propriété modifiées

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

Remarque: ceci n'a pas été testé pour une hiérarchie très profonde.

12
Pravin

NOTE les bibliothèques suivantes:

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

Je ne suis pas tout à fait sûr de bien comprendre votre question ... Je suppose que vous essayez d'identifier les clés manquantes dans le code JSON.

Si vous êtes simplement intéressé par les clés manquantes, le code ci-dessous vous aidera. Dans le cas contraire, veuillez donner un exemple des types de différences que vous essayez d'identifier.

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

REMARQUE: si la méthode this renvoie un IEnumerable vide, le JSON ACTUEL dispose de toutes les clés requises en fonction de la structure du JSON attendu.

REMARQUE: le JSON réel peut toujours avoir plus de clés que le JSON attendu n’est pas requis.

pour expliquer mes notes plus loin ...

supposons que votre JSON attendu est:

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

et que votre JSON ACTUEL est:

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

la fonction ci-dessus vous indiquera que la clé Value est manquante, mais ne mentionnera rien sur la clé SomeProp ... sauf si vous permutez les paramètres d'entrée.

3
WaseemS

Voici ma solution basée sur les idées des réponses précédentes:

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;
}
1
Dmitry Polyakov