Gyö*_*yás 9 c# json.net system.text.json
我想将我的代码从 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 ) );
}
Run Code Online (Sandbox Code Playgroud)
如果我纠正了JObject
类似于 中的 Newtonsoft System.Text.Json.JsonDocument
,并且我能够创建它,只是我不知道如何比较它的内容。
[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 ) );
}
Run Code Online (Sandbox Code Playgroud)
当然,字符串比较不是解决方案,因为 JSON 的格式无关紧要,属性的顺序也无关紧要。
System.Text.Json
.Net 3.1 中没有等效项,因此我们将不得不推出自己的。这是一种可能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
}
Run Code Online (Sandbox Code Playgroud)
使用方法如下:
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));
Run Code Online (Sandbox Code Playgroud)
笔记:
由于 Json.NET 将浮点 JSON 值解析为解析double
或decimal
解析过程中,因此JToken.DeepEquals()
将仅尾随零不同的浮点值视为相同。即以下断言通过:
Assert.IsTrue(JToken.DeepEquals(JToken.Parse("1.0"), JToken.Parse("1.00")));
Run Code Online (Sandbox Code Playgroud)
我的比较器并没有考虑这两个是相等的。我认为这是可取的,因为应用程序有时希望保留尾随零,例如在反序列化为 时decimal
,因此这种差异有时可能很重要。(有关示例,例如Json.Net 不会以相同的方式将小数序列化两次。)如果您想将此类 JSON 值视为相同,则需要修改JsonValueKind.Number
in的情况,ComputeHashCode()
并Equals(JsonElement x, JsonElement y)
在小数点后出现尾随零时进行修剪.
使上述更难的是,令人惊讶的是,JsonDocument
完全支持重复的属性名称!即它非常乐意解析{"Value":"a", "Value" : "b"}
并将两个键/值对存储在文档中。
仔细阅读https://tools.ietf.org/html/rfc8259#section-4似乎表明允许但不推荐此类对象,并且当它们出现时,对同名属性的解释可能与顺序有关。我通过按属性名称对属性列表进行稳定排序来处理此问题,然后遍历列表并比较名称和值。如果您不关心重复的属性名称,则可以通过使用单个查找字典而不是两个排序列表来提高性能。
JsonDocument
是一次性的,实际上需要根据文档进行处理:
此类利用池内存中的资源来最大程度地减少垃圾收集器 (GC) 在高使用率场景中的影响。未能正确处置此对象将导致内存无法返回到池中,这将增加框架各个部分的 GC 影响。
在你的问题中,你不这样做,但你应该这样做。
目前有一个开放的增强System.Text.Json:添加对 JSON 值进行语义比较的能力,如 JToken.DeepEquals() #33388,开发团队回答说,“这现在不在我们的路线图上。”
演示小提琴在这里。
更新
从我的 NuGet 包的1.3.0版本开始SystemTextJson.JsonDiffPatch
,您可以使用DeepEquals
扩展方法来比较JsonDocument
,JsonElement
和JsonNode
。
原答案如下
对于自 .NET 6 版本以来引入的System.Text.Json.Nodes 命名空间,当前有一个Github 问题来讨论DeepEquals
向 .NET 添加功能JsonNode
。
我将自己的实现DeepEquals
作为SystemTextJson.JsonDiffPatch
NuGet 包的一部分进行了滚动。默认情况下,扩展程序会比较 JSON 值的原始文本,但事实并非如此JToken.DeepEquals
。需要启用语义平等:
var node1 = JsonNode.Parse("[1.0]");
var node2 = JsonNode.Parse("[1]");
// false
bool equal = node1.DeepEquals(node2);
// true
bool semanticEqual = node1.DeepEquals(node2, JsonElementComparison.Semantic);
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
2676 次 |
最近记录: |