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 中支持 EnumMemberAttribute #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]
枚举值的每个组件,以及所有可能的 基础类型 (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
来自 当枚举使用
[EnumMember(Value = "custom name")]
属性;安装包 Macross.Json.Extensions
然后执行:
[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.
如果您只需要序列化一个带有自定义值名称的枚举,这可以通过创建一个适应 JsonStringEnumConverter
由构建自定义的JsonNamingPolicy
用于查找 enum 类型rel="noreferrer">[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.)
首先介绍如下转换器:
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);
}
然后装饰你的enum
:
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 core 注册转换器,请参见例如这个答案给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.
在 .Net Core 3.x 中,转换器可能无法在使用 [Flags]
枚举时按预期工作,例如:
In .Net Core 3.x the converter may not work as desired with [Flags]
enums such as:
[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"
而不是 Trick, TrickOrTreat"
.
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
调用 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.
在 .Net 5 中,此问题已修复,有关详细信息,请参阅问题 #31622.
In .Net 5 this is fixed, see Issue #31622 for details.
演示小提琴 此处.
这篇关于System.Text.Json:如何为枚举值指定自定义名称?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!