System.Text.Json:如何为枚举值指定自定义名称? [英] System.Text.Json: How do I specify a custom name for an enum value?
问题描述
使用.NET Core中的 System.Text.Json 序列化器功能,如何为枚举值指定自定义值,类似于 JsonPropertyName
?例如:
Using the System.Text.Json serializer capabilities in .NET Core, how can I specify a custom value for an enum value, similar to JsonPropertyName
? For example:
public enum Example {
Trick,
Treat,
[JsonPropertyName("Trick-Or-Treat")] // Error: Attribute 'JsonPropertyName' is not valid on this declaration type. It is only valid on 'property, indexer' declarations.
TrickOrTreat
}
推荐答案
。net-core-3.0 。当前存在一个公开问题 对JsonConverterEnum#31081 请求此功能。在此期间,您将需要创建自己的 JsonConverterFactory
可以使用属性指定的自定义值名称对枚举进行序列化。
This is not currently supported out of the box in .net-core-3.0. There is currently an open issue Support for EnumMemberAttribute in JsonConverterEnum #31081 requesting this functionality. In the interim, you will need to create your own JsonConverterFactory
that serializes enums with custom value names specified by attributes.
如果需要要使用自定义值名称往返枚举,您将需要从头开始创建通用转换器+转换器工厂。通常,这有点涉及,因为有必要处理整数和字符串值的解析,重命名 [Flags]
枚举值的每个组成部分以及所有可能的< a href = https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/language-specification/enums#enum-declarations rel = noreferrer>基础类型( byte
, short
, int
, long
, ulong
等)。
If you need to round-trip an enum with custom value names you will need to create a generic converter + converter factory from scratch. This is somewhat involved in general as it is necessary to handle parsing of integer and string values, renaming of each component of a [Flags]
enum value, and enums of all possible underlying types (byte
, short
, int
, long
, ulong
etc).
JsonStringEnumMemberConverter
来自 Macross.Json.Extensions
似乎在用 [EnumMember(Value = custom name)]
属性;安装软件包 Macross.Json.Extensions
,然后执行以下操作:
JsonStringEnumMemberConverter
from Macross.Json.Extensions
appears to provide this functionality when the enum is decorated with [EnumMember(Value = "custom name")]
attributes; install the package Macross.Json.Extensions
and then do:
[JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumMemberConverter))] // This custom converter was placed in a system namespace.
public enum Example
{
Trick,
Treat,
[EnumMember(Value = "Trick-Or-Treat")]
TrickOrTreat,
}
请参阅文档此处了解用法详细信息。
See the docs here for usage details.
或者,您可以使用Json.NET的 StringEnumConverter
作为参考模型。
Alternatively you could roll your own by using Json.NET's StringEnumConverter
as a reference model.
如果只需要 serialize 具有自定义值名称的枚举,这可以通过创建适应 JsonStringEnumConverter
缩小了 JsonNamingPolicy
用于每个枚举
类型,用于查找 [EnumMember(Value = xxx)]
属性,以及如果找到任何成员,则将成员名称映射到属性的值。 (我选择 EnumMember
,因为这是Newtonsoft支持的属性。)
If you only need to serialize an enum with custom value names this can be done more easily by creating a JsonConverterFactory
that adapts JsonStringEnumConverter
by constructing a customized JsonNamingPolicy
for each enum
type that looks for the presence of [EnumMember(Value = "xxx")]
attributes on the enum's members, and if any are found, maps the member name to the attribute's value. (I chose EnumMember
because this is the attribute supported by Newtonsoft.)
首先,引入以下转换器:
First, introduce the following converter:
public class CustomJsonStringEnumConverter : JsonConverterFactory
{
private readonly JsonNamingPolicy namingPolicy;
private readonly bool allowIntegerValues;
private readonly JsonStringEnumConverter baseConverter;
public CustomJsonStringEnumConverter() : this(null, true) { }
public CustomJsonStringEnumConverter(JsonNamingPolicy namingPolicy = null, bool allowIntegerValues = true)
{
this.namingPolicy = namingPolicy;
this.allowIntegerValues = allowIntegerValues;
this.baseConverter = new JsonStringEnumConverter(namingPolicy, allowIntegerValues);
}
public override bool CanConvert(Type typeToConvert) => baseConverter.CanConvert(typeToConvert);
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var query = from field in typeToConvert.GetFields(BindingFlags.Public | BindingFlags.Static)
let attr = field.GetCustomAttribute<EnumMemberAttribute>()
where attr != null
select (field.Name, attr.Value);
var dictionary = query.ToDictionary(p => p.Item1, p => p.Item2);
if (dictionary.Count > 0)
{
return new JsonStringEnumConverter(new DictionaryLookupNamingPolicy(dictionary, namingPolicy), allowIntegerValues).CreateConverter(typeToConvert, options);
}
else
{
return baseConverter.CreateConverter(typeToConvert, options);
}
}
}
public class JsonNamingPolicyDecorator : JsonNamingPolicy
{
readonly JsonNamingPolicy underlyingNamingPolicy;
public JsonNamingPolicyDecorator(JsonNamingPolicy underlyingNamingPolicy) => this.underlyingNamingPolicy = underlyingNamingPolicy;
public override string ConvertName (string name) => underlyingNamingPolicy == null ? name : underlyingNamingPolicy.ConvertName(name);
}
internal class DictionaryLookupNamingPolicy : JsonNamingPolicyDecorator
{
readonly Dictionary<string, string> dictionary;
public DictionaryLookupNamingPolicy(Dictionary<string, string> dictionary, JsonNamingPolicy underlyingNamingPolicy) : base(underlyingNamingPolicy) => this.dictionary = dictionary ?? throw new ArgumentNullException();
public override string ConvertName (string name) => dictionary.TryGetValue(name, out var value) ? value : base.ConvertName(name);
}
然后装饰您的枚举
:
public enum Example
{
Trick,
Treat,
[EnumMember(Value = "Trick-Or-Treat")]
TrickOrTreat,
}
,并按如下所示独立使用转换器:
And use the converter standalone as follows:
var options = new JsonSerializerOptions
{
Converters = { new CustomJsonStringEnumConverter() },
WriteIndented = true,
};
var json = JsonSerializer.Serialize(values, options);
要在asp.net内核中注册转换器,请参见此答案对 Mani Gandham 使用System.Text.Json 的JsonConverter等效。
To register the converter with asp.net core, see e.g. this answer to JsonConverter equivalent in using System.Text.Json by Mani Gandham.
注意:
-
此方法仅适用于序列化,因为
JsonConverterFactory
在反序列化期间会忽略其命名策略;请参见 System.Text.Json:JsonStringEnumConverter在反序列化期间将忽略其JsonNamingPolicy。 #31619 了解详情。
This approach only works for serialization because
JsonConverterFactory
ignores its naming policy during deserialization; see System.Text.Json: JsonStringEnumConverter ignores its JsonNamingPolicy during deserialization. #31619 for details.
使用 [Flags] 枚举,例如:
[Flags]
public enum Example
{
Trick = (1<<0),
Treat = (1<<1),
[EnumMember(Value = "Trick-Or-Treat")]
TrickOrTreat = (1<<2),
}
简单值,例如 Example.TrickOrTreat
已正确重命名,但复合值如 Example.Trick |。 Example.TrickOrTreat
不是。后者的结果应为 Trick,Trick-Or-Treat
,而不是 。
Simple values like Example.TrickOrTreat
are renamed properly, but composite values like Example.Trick | Example.TrickOrTreat
are not. The result for the latter should be "Trick, Trick-Or-Treat"
but is instead "Trick, TrickOrTreat"
.
问题的原因是底层 JsonConverterEnum< T>
键入 T
调用 ConvertName
一次使用构造的复合名称,而不是多次使用复合名称的每个组成部分。如果需要解决方法,请在 DictionaryLookupNamingPolicy.ConvertName()
中尝试将传入名称拆分为以逗号分隔的组件,重新映射每个组件,然后重新组合结果。
The cause of the problem is that the underlying JsonConverterEnum<T>
for each specific enum type T
calls ConvertName
once with the constructed composite name rather than multiple times with each component of the composite name. If a workaround is required, in DictionaryLookupNamingPolicy.ConvertName()
you could try splitting the incoming name into comma-separated components, remapping each component, then recombining the results.
为了进行比较,Json.NET的 StringEnumConverter
调用等效方法 NamingStrategy.ResolvePropertyName(string name)
出现在复合标志值的每个组件上,这似乎更正确。
For comparison, Json.NET's StringEnumConverter
calls the equivalent method NamingStrategy.ResolvePropertyName(string name)
on every component of a composite flag value, which seems more correct.
演示小提琴此处。
这篇关于System.Text.Json:如何为枚举值指定自定义名称?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!