System.Text.Json:如何为枚举值指定自定义名称?

时间:2019-11-26 22:00:00

标签: c# .net .net-core .net-core-3.0 system.text.json

使用.NET Core中的 System.Text.Json 序列化程序功能,如何为枚举值指定自定义值,类似于JsonPropertyName?例如:

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
}

3 个答案:

答案 0 :(得分:3)

当前中的属性不支持此功能。当前有一个公开问题 Support for EnumMemberAttribute in JsonConverterEnum #41578 要求该功能。

在此期间,您可以为每个enum类型构造一个自定义的JsonConverterFactory,以寻找{{3}的存在,从而创建自己的JsonStringEnumConverter以适应JsonNamingPolicy }}枚举成员的属性,如果找到,则将成员的名称映射到该属性的值。

首先,介绍以下转换器:

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;

    public override string ConvertName (string name)
    {
        if (!dictionary.TryGetValue(name, out var value))
            value = base.ConvertName(name);
        return value;
    }
}

然后装饰您的enum

public enum Example 
{
  Trick,
  Treat,
  [EnumMember(Value = "Trick-Or-Treat")]
   TrickOrTreat,
}

并独立使用转换器,如下所示:

var options = new JsonSerializerOptions
{
    Converters = { new CustomJsonStringEnumConverter() },
    WriteIndented = true,
};
var json = JsonSerializer.Serialize(values, options);

要将转换器注册到asp.net内核,请参见例如由[EnumMember(Value = "xxx")]this answer JsonConverter equivalent in using System.Text.Json

注意:

  • 对于[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",但应为"Trick, TrickOrTreat"

    问题的原因是,每种特定枚举类型T的基础Mani Gandham用构造的复合名称调用JsonConverterEnum<T>一次,而不用复合名称的每个组成部分多次调用ConvertName 。如果需要解决方法,则可以在DictionaryLookupNamingPolicy.ConvertName()中尝试将传入名称拆分为以逗号分隔的组件,重新映射每个组件,然后重新组合结果。

    为了进行比较,Json.NET的StringEnumConverter在复合标志值的每个组件上调用等效方法NamingStrategy.ResolvePropertyName(string name),这似乎更正确。

演示小提琴here

答案 1 :(得分:0)

对于.NET 5:

services.AddControllers()
    .AddJsonOptions(opts => opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));

答案 2 :(得分:-2)

中,Microsoft 添加了对通过 JsonStringEnumConverter Class 反序列化枚举的支持。

像这样装饰你的枚举值:

using System.Runtime.Serialization;
public enum VipStatus
{
    [EnumMember(Value = @"IS_VIP")]
    VIP = 1,

    [EnumMember(Value = @"IS_NOT_VIP")]
    NonVIP = 2,
}

给定一个这样的类:

class MyClass {
    public VipStatus MyVipStatus { get; set; }
}

您可以使用 JsonStringEnumConverter 内联来序列化类的实例,如下所示:

using System.Text.Json;
using System.Text.Json.Serialization;
// ...

var myObjectWithEnums = new MyClass()
{
    MyVipStatus = VipStatus.NonVIP
};

var options = new JsonSerializerOptions();

// Configures serialization to allow strings to be accepted and auto-converted to enum values.
options.Converters.Add(new JsonStringEnumConverter());

var json = JsonSerializer.Serialize(myObjectWithEnums, options);
// serialized output is: { "myVipStatus": "IS_NOT_VIP"}

如果您使用的是 ASP.NET Core 5,那么您可以在启动时配置应用程序以使用 JsonStringEnumConverter 来序列化所有传入请求:

public async void ConfigureServices(IServiceCollection services) {
    // ...
    services
        .AddControllers()
        .AddJsonOptions(options => {
            // Configures serialization to allow strings to be accepted and auto-converted to enum values.
            options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
        }
    // ...
});

更多阅读:How to serialize and deserialize (marshal and unmarshal) JSON in .NET Core。如果您在 ASP.NET 中工作,那么这也很有趣:Web defaults for JsonSerializerOptions