问题
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:
[JsonConverter]
applied to a property.- A converter added to the Converters collection.
[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