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

半世苍凉 提交于 2021-02-05 09:01:32

问题


I am developing the ASP.NET Core Application using the latest .NET Core 3.1.1 and System.Text.Json which was previously using the Newtonsoft.Json. As recommended in the Microsoft Migration guide I have done the changes. Also, as most of my enums need to be serialized as String I have configured my Startup.cs ConfigureServices to use the JsonStringEnumConverter globally.

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;
                    });
}

But recently, after the release we realized that only a few enums are given out as numerals in json through our API. As these APIs are consumed externally, changing the numerals to strings might be a costly stuff.

So, is there a way to ignore the universal for some enum properties like the decoration with the [JsonIgnore] attribute?


回答1:


JsonStringEnumConverter is actually a subclass of JsonConverterFactory. It manufactures a specific JsonConverterEnum for each concrete enum type encountered during serialization that, in turn, serializes that specific enum type as a string.

If you don't want some specific enum type to be serialized as a string, you can use the decorator pattern and create your own converter factory that decorates a JsonStringEnumConverter but prevents that enum type from being converted as follows:

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);
}

Then use it in options as follows:

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

Notes:

  • If maintaining a list of enum types to serialize as integers is inconvenient, you could mark the enum types to be serialized as integers with some custom attribute, then exclude types marked with that attribute from within CanConvert(CanConvert(Type typeToConvert).

  • The decorator pattern is required because JsonStringEnumConverter is sealed.

Mockup fiddle #1 here.

Alternatively, if you don't want some specific enum property to be serialized as a string, you can apply a converter to the property using JsonConverterAttribute that ignores the incoming JsonSerializerOptions and generates a default serialization instead:

/// <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!
    } 
}

And apply it to your model as follows:

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

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

Notes:

  • This solution takes advantage of the 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.

Mockup fiddle #2 here.



来源:https://stackoverflow.com/questions/59828937/exclude-an-enum-property-of-a-model-from-using-the-jsonstringenumconverter-which

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!