我正在使用JsonNode.ToJsonString(...)在我的单元测试中。 不幸的是,序列化字符串中的属性顺序似乎取决于添加子节点和值节点的运行时顺序。
但是,表达单元测试期望时,顺序将是确定性的,以允许等于期望。
我查看了 serializer options但没有找到任何相关内容
问题
有没有办法让 JsonNode.ToJsonString(...) 按字母顺序生成属性?
最佳答案
没有内置功能可以执行此操作,但您可以创建自定义 JsonConverter<JsonNode>
在写入属性时按字母顺序排列。然后您可以使用 JsonSerializer.Serialize(node, options)
将节点序列化为 JSON将转换器添加到 JsonSerializerOptions.Converters
.
首先,定义以下扩展方法和转换器:
public static partial class JsonExtensions
{
readonly static JsonSerializerOptions defaultOptions = new () { Converters = { AlphabeticJsonNodeConverter.Instance } };
public static string ToAlphabeticJsonString(this JsonNode? node, JsonSerializerOptions? options = default)
{
if (options == null)
options = defaultOptions;
else
{
options = new JsonSerializerOptions(options);
options.Converters.Insert(0, AlphabeticJsonNodeConverter.Instance);
}
return JsonSerializer.Serialize(node, options);
}
}
public class AlphabeticJsonNodeConverter : JsonConverter<JsonNode>
{
public static AlphabeticJsonNodeConverter Instance { get; } = new AlphabeticJsonNodeConverter();
public override bool CanConvert(Type typeToConvert) => typeof(JsonNode).IsAssignableFrom(typeToConvert) && typeToConvert != typeof(JsonValue);
public override void Write(Utf8JsonWriter writer, JsonNode? value, JsonSerializerOptions options)
{
switch (value)
{
case JsonObject obj:
writer.WriteStartObject();
foreach (var pair in obj.OrderBy(p => p.Key, StringComparer.Ordinal))
{
writer.WritePropertyName(pair.Key);
Write(writer, pair.Value, options);
}
writer.WriteEndObject();
break;
case JsonArray array: // We need to handle JsonArray explicitly to ensure that objects inside arrays are alphabetized
writer.WriteStartArray();
foreach (var item in array)
Write(writer, item, options);
writer.WriteEndArray();
break;
case null:
writer.WriteNullValue();
break;
default: // JsonValue
value.WriteTo(writer, options);
break;
}
}
public override JsonNode? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => JsonNode.Parse(ref reader);
}
现在您将能够执行以下操作:
var options = new JsonSerializerOptions
{
WriteIndented = true, // Or false, if you prefer
};
var json = node.ToAlphabeticJsonString(options);
注释:
我用了
StringComparer.Ordinal
因为它似乎符合 RFC 8785: JSON Canonicalization Scheme (JCS) 的排序要求.虽然上述转换器适用于任何
JsonNode
从JsonNode.Parse()
返回,从JsonElement
创建,或由原始键和值构建,如doc example所示,也可以创建JsonValue
以任何 POCO 作为其值,例如:var node = JsonValue.Create(new { ZProperty = "foo", AProperty = "bar" })!;
这种情况会被格式化为 JSON 作为对象,而不是原始值:
{ "ZProperty": "foo", "AProperty": "bar" }
我不知道为什么微软提供这样的功能,但是当将这样的节点格式化为 JSON 时,代码 invokes the serializer序列化其内容。因此,在这种奇怪的情况下,不会调用上面的转换器来按字母顺序排列属性。
作为按字母顺序排列的替代方案
JsonNode
写入时的属性,您可以使用JsonElementComparer
来自this answer至 What is equivalent in JToken.DeepEquals in System.Text.Json? 比较 JSON 元素并忽略属性顺序。
演示 fiddle here .
关于c# - 如何让 JsonNode.ToJsonString(...) 按字母顺序生成属性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75724756/