System.Text.Json:如何为枚举值指定自定义名称? [英] System.Text.Json: How do I specify a custom name for an enum value?

查看:28
本文介绍了System.Text.Json:如何为枚举值指定自定义名称?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用 .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
}

推荐答案

.目前有一个未解决的问题 在 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] 枚举值的每个组件,以及所有可能的 基础类型 (byteshortintlongulong 等).

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.

注意事项:

  • 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆