c# - 如何让 JsonNode.ToJsonString(...) 按字母顺序生成属性?

标签 c# .net-core system.text.json jsonnode

我正在使用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) 的排序要求.

  • 虽然上述转换器适用于任何 JsonNodeJsonNode.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/

相关文章:

c# - 是否可以使用 Rider 和 launchSettings - 可执行文件调试 .net core

c# - WinRT - MessageDialog.ShowAsync 将在我的自定义类中抛出 UnauthorizedAccessException

c# - C# 中的生成器?

c# - .net core 中的 ManagedOpenSsl 抛出 "System.BadImageFormatException:"

c# - 在div或元素外部单击以在Blazor上将其关闭的事件

c# - 为什么 System.Text Json Serializer 不会序列化这个通用属性,但 Json.NET 会?

c# - 拆分字符串然后解析为整数

ajax - .NET Core 从 Razor 页面到 Controller 进行 AJAX 调用

c# - 使用System.Text.Json反序列化匿名类型

c# - 如何使用 System.Text.Json 将 ReadOnlySpan<char> 反序列化为对象