web-dev-qa-db-ja.com

.NETコア3:JsonPropertyNameのシリアル化の順序(System.Text.Json.Serialization)

.NET Core 3への移行中に、Newtonsoft.Jsonシリアル化からSystem.Text.Json.Serializationに切り替えました。 JsonPropertyName属性を引き続き使用したいすべての機能の中で。

Newtonsoftのバージョンが許可されました シリアル化された属性の順序

[JsonProperty(Order = 1)]
public bool Deleted { get; set; }

[JsonProperty(Order = 2)]
public DateTime DeletedDate { get; set; }

System.Text.Json.Serializationで同じことを達成する方法はありますか?

4
Sergey Nikitin

この機能は.NET Coreには実装されていませんが、カスタムJsonConverterを作成することにより、必要な順序を適用できます。それを実現する方法はいくつかあります。以下は私が思いついた実装です。

説明-JsonPropertyOrderConverterは、カスタムオーダー値が適用された少なくとも1つのプロパティを持つ型を処理します。これらのタイプごとに、元のオブジェクトを特定の順序で設定されたプロパティを持つExpandoObjectに変換するソーター関数を作成してキャッシュします。 ExpandoObjectはプロパティの順序を維持するため、さらにシリアル化するためにJsonSerializerに戻すことができます。コンバーターは、プロパティのシリアル化に適用されるJsonPropertyNameAttributeおよびJsonPropertyOrderAttribute属性も尊重します。

ソーター関数はPropertyInfoオブジェクトを処理することに注意してください。これにより、レイテンシが追加される可能性があります。シナリオでパフォーマンスが重要な場合は、式ツリーに基づくFunction<object, object>ソーターの実装を検討してください。

class Program
{
    static void Main(string[] args)
    {
        var test = new Test { Bar = 1, Baz = 2, Foo = 3 };

        // Add JsonPropertyOrderConverter to enable ordering
        var opts = new JsonSerializerOptions();
        opts.Converters.Add(new JsonPropertyOrderConverter());

        var serialized = JsonSerializer.Serialize(test, opts);

        // Outputs: {"Bar":1,"Baz":2,"Foo":3}
        Console.WriteLine(serialized);
    }
}

class Test
{
    [JsonPropertyOrder(1)]
    public int Foo { get; set; }

    [JsonPropertyOrder(-1)]
    public int Bar { get; set; }

    // Default order is 0
    public int Baz { get; set; }

}

/// <summary>
/// Sets a custom serialization order for a property.
/// The default value is 0.
/// </summary>
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
sealed class JsonPropertyOrderAttribute : Attribute
{
    public int Order { get; }

    public JsonPropertyOrderAttribute(int order)
    {
        Order = order;
    }
}

/// <summary>
/// For Serialization only.
/// Emits properties in the specified order.
/// </summary>
class JsonPropertyOrderConverter : JsonConverter<object>
{
    delegate ExpandoObject SorterFunc(object value, bool ignoreNullValues);

    private static readonly ConcurrentDictionary<Type, SorterFunc> _sorters
        = new ConcurrentDictionary<Type, SorterFunc>();

    public override bool CanConvert(Type typeToConvert)
    {
        // Converter will not run if there is no custom order applied
        var sorter = _sorters.GetOrAdd(typeToConvert, CreateSorter);
        return sorter != null;
    }

    public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        throw new NotSupportedException();
    }

    public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
    {
        // Resolve the sorter.
        // It must exist here (see CanConvert).
        var sorter = _sorters.GetOrAdd(value.GetType(), CreateSorter);

        // Convert value to an ExpandoObject
        // with a certain property order
        var sortedValue = sorter(value, options.IgnoreNullValues);

        // Serialize the ExpandoObject
        JsonSerializer.Serialize(writer, (IDictionary<string, object>)sortedValue, options);
    }

    private SorterFunc CreateSorter(Type type)
    {
        // Get type properties ordered according to JsonPropertyOrder value
        var sortedProperties = type
            .GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Where(x => x.GetCustomAttribute<JsonIgnoreAttribute>(true) == null)
            .Select(x => new
            {
                Info = x,
                Name = x.GetCustomAttribute<JsonPropertyNameAttribute>(true)?.Name ?? x.Name,
                Order = x.GetCustomAttribute<JsonPropertyOrderAttribute>(true)?.Order ?? 0
            })
            .OrderBy(x => x.Order)
            .ToList();

        // If all properties have the same order,
        // there is no sense in explicit sorting
        if (!sortedProperties.Any(x => x.Order != 0))
        {
            return null;
        }

        // Return a function assigning property values
        // to an ExpandoObject in a specified order
        return new SorterFunc((src, ignoreNullValues) =>
        {
            IDictionary<string, object> dst = new ExpandoObject();

            foreach (var prop in sortedProperties)
            {
                var propValue = prop.Info.GetValue(src);

                if (!ignoreNullValues || !(propValue is null))
                {
                    dst.Add(prop.Name, propValue);
                }
            }

            return (ExpandoObject)dst;
        });
    }
}
2
AndreyCh