programing

C#의 Newtonsoft를 사용하여 JSON 차이를 찾아 반환하시겠습니까?

newnotes 2023. 3. 26. 11:47
반응형

C#의 Newtonsoft를 사용하여 JSON 차이를 찾아 반환하시겠습니까?

Newtonsoft를 사용하여 비교할 때 일치하지 않는 JSON 부품 목록을 받고 싶습니다.

다음 코드를 비교합니다.

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

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

하지만 diff를 반환할 수 있는 것을 찾을 수 없습니다.

향후 질문들을 돕기 위해서입니다.내가 우연히 발견한 멋진 json diff 도구가 있어.json 구조의 diff/patch에 대해 완벽하게 작동합니다.

jsondiffpatch.net Nuget 패키지도 있습니다.

용도는 간단합니다.

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

여기 제가 쓴 재귀 버전이 있습니다.2개의 JObject를 사용하여 CompareObjects를 호출하면 차이 목록이 반환됩니다.2개의 JAray를 사용하여 Compare Arrays를 호출하여 어레이를 비교합니다.배열과 개체를 서로 중첩할 수 있습니다.

업데이트: @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;
    }

이전 답변의 아이디어를 바탕으로 한 솔루션이 있습니다.

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

이것은 비교적 오래된 질문이지만 원하는 결과가 정확히 어떤 속성 값이 변경되었는지 가정할 때 이 문제를 해결할 수 있는 방법 중 하나를 게시합니다.

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

주의: 이것은 매우 상세한 계층에 대해서는 테스트되지 않았습니다.

이것은 꽤 오래된 스레드입니다만, 몇 달 전에 신뢰할 수 있는 툴을 찾으러 왔으나 찾을 수 없었기 때문에, 직접 작성했기 때문에, 다음과 같은 것을 보고 있으면 사용해 주세요.

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

다음의 라이브러리에 주의해 주세요.

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

실제 JSON은 다음과 같습니다.

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

위의 함수는 Value 키가 없음을 알려 주지만 입력 파라미터를 교환하지 않는 한 SomeProp 키에 대해서는 언급하지 않습니다.

객체 배열에서 더 정확하게 변환했습니다.

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

여기 있는 어떤 대답도 내가 원하는 것이 아니었다.

다음으로 비교한2개의 오브젝트 각각에 대해 JObject를 반환하는 메서드를 나타냅니다.JObject에는 서로 다른 속성만 포함됩니다.이 기능은 엔티티에 대한 변경 내용을 검색하고 전후 스냅샷을 저장하는 데 유용합니다(JObject 직렬화).

NB: 변경 검색은 최상위 속성에서만 수행됩니다.

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

    }

언급URL : https://stackoverflow.com/questions/24876082/find-and-return-json-differences-using-newtonsoft-in-c

반응형