You should use the factory pattern to manufacture specific converters for each Enum type as you encounter it:
public class CustomEnumConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum; // Add anything additional here such as typeToConvert.IsEnumWithDescription() to check for description attributes.
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) =>
(JsonConverter)Activator.CreateInstance(typeof(CustomConverter<>).MakeGenericType(typeToConvert))!;
class CustomConverter<T> : JsonConverter<T> where T : struct, Enum
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
reader.GetString()!.GetEnumValue<T>()!;
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) =>
writer.WriteStringValue(value.GetDescription<T>());
}
}
A factory converter can be applied directly to properties as well as long as the factory can return a converter for the property's type, so there is no need to make the inner converter public:
public class Model
{
[JsonConverter(typeof(CustomEnumConverter))]
public SomeEnum? SomeEnum { get; set; }
}
In fact, the built-in JsonStringEnumConverter
is also implemented via the factory pattern.
Demo fiddle here.