使用在启动时全局设置的 JsonStringEnumConverter 排除模型的枚举 属性?

Exclude an enum property of a Model from using the JsonStringEnumConverter which is globally set at the Startup?

我正在使用最新的 .NET Core 3.1.1 和之前使用 Newtonsoft.JsonSystem.Text.Json 开发 ASP.NET 核心应用程序。按照 Microsoft Migration guide 中的建议 我已经完成了更改。此外,由于我的大部分枚举都需要序列化为字符串,因此我已将 Startup.cs ConfigureServices 配置为全局使用 JsonStringEnumConverter

public void ConfigureServices(IServiceCollection services)
{
    // lines omitted for brevity

    services.AddControllers()
                .AddJsonOptions(options =>
                    {
                        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
                        options.JsonSerializerOptions.IgnoreNullValues = true;
                        options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
                    });
}

但最近,在发布后我们意识到只有少数枚举通过我们的 API 在 json 中作为数字给出。由于这些 API 是外部消耗的,将数字更改为字符串可能是一件代价高昂的事情。

那么,有没有办法忽略某些枚举属性的通用性,例如带有 [JsonIgnore] 属性的装饰?

JsonStringEnumConverter is actually a subclass of JsonConverterFactory. It manufactures a specific JsonConverterEnum 对于在序列化过程中遇到的每个具体 enum 类型,依次将该特定 enum 类型序列化为字符串。

如果您不想将某些特定的 enum type 序列化为字符串,您可以使用 decorator pattern 并创建自己的转换器装饰 JsonStringEnumConverter 但阻止 enum 类型按如下方式转换的工厂:

public class OptOutJsonConverterFactory : JsonConverterFactoryDecorator
{
    readonly HashSet<Type> optOutTypes;

    public OptOutJsonConverterFactory(JsonConverterFactory innerFactory, params Type [] optOutTypes) : base(innerFactory) => this.optOutTypes = optOutTypes.ToHashSet();

    public override bool CanConvert(Type typeToConvert) => base.CanConvert(typeToConvert) && !optOutTypes.Contains(typeToConvert);
}

public class JsonConverterFactoryDecorator : JsonConverterFactory
{
    readonly JsonConverterFactory innerFactory;

    public JsonConverterFactoryDecorator(JsonConverterFactory innerFactory)
    {
        if (innerFactory == null)
            throw new ArgumentNullException(nameof(innerFactory));
        this.innerFactory = innerFactory;
    }

    public override bool CanConvert(Type typeToConvert) => innerFactory.CanConvert(typeToConvert);

    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => innerFactory.CreateConverter(typeToConvert, options);
}

然后在选项中使用如下:

options.Converters.Add(new OptOutJsonConverterFactory(new JsonStringEnumConverter(), 
                                                      // Add here all enum types to serialize as integers:
                                                      typeof(SomeEnumNotToSerializeAsAString)
                                                      //, ...
                                                     ));

备注:

  • 如果维护要序列化为整数的枚举类型列表不方便,您可以使用一些自定义标记要序列化为整数的枚举类型 attribute,然后排除标记为整数的类型CanConvert(CanConvert(Type typeToConvert).

  • 中的属性
  • 装饰器模式是必需的,因为JsonStringEnumConverter是密封的。

模型 fiddle #1 here.

或者,如果您不想将某些特定的 enum 属性 序列化为字符串,您可以对 属性 使用 JsonConverterAttribute 忽略传入的 JsonSerializerOptions 并生成默认序列化:

/// <summary>
/// Apply this converter to a property to force the property to be serialized with default options.  
/// This converter can ONLY be applied to a property; setting it in options or on a type may cause a stack overflow exception!
/// </summary>
/// <typeparam name="T">the property's declared return type</typeparam>
public class SerializePropertyAsDefaultConverter<T> : JsonConverter<T>
{
    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return JsonSerializer.Deserialize<T>(ref reader); // Ignore the incoming options!
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        JsonSerializer.Serialize(writer, value); // Ignore the incoming options!
    } 
}

并将其应用到您的模型中,如下所示:

public class Model
{
    public StringEnum StringEnum { get; set; }

    [JsonConverter(typeof(SerializePropertyAsDefaultConverter<SomeEnumNotToSerializeAsAString>))]
    public SomeEnumNotToSerializeAsAString SomeEnumNotToSerializeAsAString { get; set; }
}

备注:

  • 此解决方案利用了 documented precedence for converters:

    1. [JsonConverter] applied to a property.
    2. A converter added to the Converters collection.
    3. [JsonConverter] applied to a custom value type or POCO.

模型 fiddle #2 here.