问题描述
在迁移到 .NET Core 3 时,我已从 Newtonsoft.Json 序列化切换到 System.Text.Json.Serialization.在所有功能中,我想继续使用 JsonPropertyName 属性.
While migrating to .NET Core 3 I've switched from Newtonsoft.Json serialization to System.Text.Json.Serialization. Of all the features I want to continue using JsonPropertyName attribute.
Newtonsoft 版本允许序列化属性排序:
Newtonsoft version allowed ordering of serialized attributes:
[JsonProperty(Order = 1)]
public bool Deleted { get; set; }
[JsonProperty(Order = 2)]
public DateTime DeletedDate { get; set; }
有没有办法在 System.Text.Json.Serialization 中实现相同的功能?
Is there a way to achieve the same in System.Text.Json.Serialization?
推荐答案
虽然 .NET Core 中没有实现此功能,但我们可以通过创建自定义 JsonConverter
来应用所需的排序.有几种方法可以实现这一目标.下面是我想出的实现.
While this feature is not implemented in .NET Core, we can apply desired ordering by creating a custom JsonConverter
. There are a few ways how that can be achievable. Below is the implementation I've came up with.
说明 - JsonPropertyOrderConverter
处理具有至少一个应用了自定义订单值的属性的类型.对于这些类型中的每一种,它都会创建并缓存一个排序函数,该函数将原始对象转换为 ExpandoObject
,并按特定顺序设置属性.ExpandoObject
维护属性的顺序,因此可以将其传递回 JsonSerializer
以进行进一步的序列化.转换器还尊重应用于序列化属性的 JsonPropertyNameAttribute
和 JsonPropertyOrderAttribute
属性.
Explanation - the JsonPropertyOrderConverter
handles the types having at least one property with a custom order value applied. For each of those types, it creates and caches a sorter function that converts an original object into an ExpandoObject
with the properties set in a specific order. ExpandoObject
maintains the order of properties, so it can be passed back to JsonSerializer
for further serialization. The converter also respects JsonPropertyNameAttribute
and JsonPropertyOrderAttribute
attributes applied to serializing properties.
请注意,Sorter 函数处理可能会增加一些额外延迟的 PropertyInfo
对象.如果性能在您的场景中至关重要,请考虑基于表达式树实现 Function
排序器.
Please note that Sorter functions deal with PropertyInfo
objects that can add some extra latency. If the performance is critical in your scenario, consider implementing Function<object, object>
sorter based on Expression trees.
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,
IsExtensionData = x.GetCustomAttribute<JsonExtensionDataAttribute>(true) != null
})
.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();
var isExtensionDataProcessed = false;
foreach (var prop in sortedProperties)
{
var propValue = prop.Info.GetValue(src);
if (prop.IsExtensionData)
{
if (propValue is IDictionary extensionData)
{
if (isExtensionDataProcessed)
{
throw new InvalidOperationException($"The type '{src.GetType().FullName}' cannot have more than one property that has the attribute '{typeof(JsonExtensionDataAttribute).FullName}'.");
}
foreach (DictionaryEntry entry in extensionData)
{
dst.Add((string)entry.Key, entry.Value);
}
}
isExtensionDataProcessed = true;
}
else if (!ignoreNullValues || !(propValue is null))
{
dst.Add(prop.Name, propValue);
}
}
return (ExpandoObject)dst;
});
}
}
这篇关于.NET core 3:JsonPropertyName (System.Text.Json.Serialization) 的序列化顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!