gpt4 book ai didi

c# - System.Text.Json中的JToken.DeepEquals中的等效项是什么?

转载 作者:行者123 更新时间:2023-12-03 17:08:25 27 4
gpt4 key购买 nike

我想将代码从Newtonsoft Json.Net迁移到Microsoft标准System.Text.Json。但是我找不到JToken.DeepEqual的替代方法
基本上,代码必须在单元测试中比较两个JSON。引用JSON和结果JSON。我在Newtonsoft中使用了该机制来创建两个JObject,然后将它们与JToken.DeepEqual进行比较。这是示例代码:

[TestMethod]
public void ExampleUnitTes()
{
string resultJson = TestedUnit.TestedMethod();
string referenceJson =
@"
{
...bla bla bla...
...some JSON Content...
...bla bla bla...
}";

JObject expected = ( JObject )JsonConvert.DeserializeObject( referenceJson );
JObject result = ( JObject )JsonConvert.DeserializeObject( resultJson );
Assert.IsTrue( JToken.DeepEquals( result, expected ) );
}
如果我正确类似于 JObject的Newtonsoft System.Text.Json.JsonDocument,并且能够创建它,只是我不知道如何比较它的内容。
System.Text.Json.JsonDocument expectedDoc = System.Text.Json.JsonDocument.Parse( referenceJson );
System.Text.Json.JsonDocument resultDoc = System.Text.Json.JsonDocument.Parse( json );

Compare???( expectedDoc, resulDoc );
当然,字符串比较不是解决方案,因为JSON的格式无关紧要,属性的顺序也无关紧要。

最佳答案

从.Net 3.1开始, System.Text.Json 中没有等效项,因此我们将不得不自己滚动。这是一种可能的IEqualityComparer<JsonElement>:

public class JsonElementComparer : IEqualityComparer<JsonElement>
{
public JsonElementComparer() : this(-1) { }

public JsonElementComparer(int maxHashDepth) => this.MaxHashDepth = maxHashDepth;

int MaxHashDepth { get; } = -1;

#region IEqualityComparer<JsonElement> Members

public bool Equals(JsonElement x, JsonElement y)
{
if (x.ValueKind != y.ValueKind)
return false;
switch (x.ValueKind)
{
case JsonValueKind.Null:
case JsonValueKind.True:
case JsonValueKind.False:
case JsonValueKind.Undefined:
return true;

// Compare the raw values of numbers, and the text of strings.
// Note this means that 0.0 will differ from 0.00 -- which may be correct as deserializing either to `decimal` will result in subtly different results.
// Newtonsoft's JValue.Compare(JTokenType valueType, object? objA, object? objB) has logic for detecting "equivalent" values,
// you may want to examine it to see if anything there is required here.
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Linq/JValue.cs#L246
case JsonValueKind.Number:
return x.GetRawText() == y.GetRawText();

case JsonValueKind.String:
return x.GetString() == y.GetString(); // Do not use GetRawText() here, it does not automatically resolve JSON escape sequences to their corresponding characters.

case JsonValueKind.Array:
return x.EnumerateArray().SequenceEqual(y.EnumerateArray(), this);

case JsonValueKind.Object:
{
// Surprisingly, JsonDocument fully supports duplicate property names.
// I.e. it's perfectly happy to parse {"Value":"a", "Value" : "b"} and will store both
// key/value pairs inside the document!
// A close reading of https://tools.ietf.org/html/rfc8259#section-4 seems to indicate that
// such objects are allowed but not recommended, and when they arise, interpretation of
// identically-named properties is order-dependent.
// So stably sorting by name then comparing values seems the way to go.
var xPropertiesUnsorted = x.EnumerateObject().ToList();
var yPropertiesUnsorted = y.EnumerateObject().ToList();
if (xPropertiesUnsorted.Count != yPropertiesUnsorted.Count)
return false;
var xProperties = xPropertiesUnsorted.OrderBy(p => p.Name, StringComparer.Ordinal);
var yProperties = yPropertiesUnsorted.OrderBy(p => p.Name, StringComparer.Ordinal);
foreach (var (px, py) in xProperties.Zip(yProperties))
{
if (px.Name != py.Name)
return false;
if (!Equals(px.Value, py.Value))
return false;
}
return true;
}

default:
throw new JsonException(string.Format("Unknown JsonValueKind {0}", x.ValueKind));
}
}

public int GetHashCode(JsonElement obj)
{
var hash = new HashCode(); // New in .Net core: https://docs.microsoft.com/en-us/dotnet/api/system.hashcode
ComputeHashCode(obj, ref hash, 0);
return hash.ToHashCode();
}

void ComputeHashCode(JsonElement obj, ref HashCode hash, int depth)
{
hash.Add(obj.ValueKind);

switch (obj.ValueKind)
{
case JsonValueKind.Null:
case JsonValueKind.True:
case JsonValueKind.False:
case JsonValueKind.Undefined:
break;

case JsonValueKind.Number:
hash.Add(obj.GetRawText());
break;

case JsonValueKind.String:
hash.Add(obj.GetString());
break;

case JsonValueKind.Array:
if (depth != MaxHashDepth)
foreach (var item in obj.EnumerateArray())
ComputeHashCode(item, ref hash, depth+1);
else
hash.Add(obj.GetArrayLength());
break;

case JsonValueKind.Object:
foreach (var property in obj.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal))
{
hash.Add(property.Name);
if (depth != MaxHashDepth)
ComputeHashCode(property.Value, ref hash, depth+1);
}
break;

default:
throw new JsonException(string.Format("Unknown JsonValueKind {0}", obj.ValueKind));
}
}

#endregion
}

如下使用它:
var comparer = new JsonElementComparer();
using var doc1 = System.Text.Json.JsonDocument.Parse(referenceJson);
using var doc2 = System.Text.Json.JsonDocument.Parse(resultJson);
Assert.IsTrue(comparer.Equals(doc1.RootElement, doc2.RootElement));

笔记:
  • 由于Json.NET在解析期间将浮点JSON值解析为doubledecimal,因此JToken.DeepEquals()认为仅尾随零不同的浮点值是相同的。 IE。以下断言通过:
    Assert.IsTrue(JToken.DeepEquals(JToken.Parse("1.0"), JToken.Parse("1.00")));

    我的比较器确实而不是认为这两个相等。我认为这是可取的,因为应用程序有时希望保留结尾的零,例如反序列化为decimal时,因此有时这种差异可能很重要。 (有关示例,请参见Json.Net not serializing decimals the same way twice。)如果您希望此类JSON值相同,则需要修改JsonValueKind.NumberComputeHashCode()Equals(JsonElement x, JsonElement y)的大小写,以在小数点后出现时修剪尾随零。
  • 令人惊讶的是,JsonDocument完全支持重复的属性名称,这使上述工作变得更加困难! IE。解析{"Value":"a", "Value" : "b"}非常高兴,并将两个键/值对存储在文档中。

    仔细阅读https://tools.ietf.org/html/rfc8259#section-4似乎表明允许但不建议使用此类对象,并且当它们出现时,对相同名称的属性的解释可能与顺序有关。我通过按属性名称对属性列表进行稳定排序,然后遍历列表并比较名称和值来处理此问题。如果您不关心重复的属性名称,则可以通过使用单个查找字典而不是两个排序列表来提高性能。
  • JsonDocument是一次性的,实际上需要根据docs进行处理:

    This class utilizes resources from pooled memory to minimize the impact of the garbage collector (GC) in high-usage scenarios. Failure to properly dispose this object will result in the memory not being returned to the pool, which will increase GC impact across various parts of the framework.



    在您的问题中,您不这样做,但您应该这样做。
  • 当前有一个开放的增强功能System.Text.Json: add ability to do semantic comparisons of JSON values à la JToken.DeepEquals() #33388,开发团队回答说:“目前我们的路线图上没有此功能。”

  • 演示 fiddle here

    关于c# - System.Text.Json中的JToken.DeepEquals中的等效项是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60580743/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com