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

查看:146
本文介绍了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#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.

注意:

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

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