1

I have an enum extension that allows for enums to be transformed into their [Description] value, as in:

    private enum WithDescription
    {
        [Description("Nothing")] None = 0,
        [Description("Some of it")] Some,
        [Description("All of it")] All
    }
public static class EnumExtensions
{
    public static string GetDescription<T>(this T enumerationValue) where T : struct, Enum
    {
        var type = enumerationValue.GetType();
        if (!type.IsEnum)
        {
            throw new ArgumentException($"{nameof(T)} must be of type Enum.");
        }

        var memberInfo = type.GetMember(enumerationValue.ToString());
        if (memberInfo.Length > 0)
        {
            var attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

            if (attrs.Length > 0)
            {
                return ((DescriptionAttribute) attrs[0]).Description;
            }
        }

        return enumerationValue.ToString();
    }

    public static T GetEnumByDescription<T>(this string text) where T : struct, Enum
    {
        var type = typeof(T);
        if (!type.IsEnum)
        {
            throw new ArgumentException($"{nameof(T)} must be of type Enum.");
        }

        MemberInfo[] members = type.GetMembers(BindingFlags.Public | BindingFlags.Static);
        foreach (MemberInfo member in members)
        {
            var attrs = member.GetCustomAttributes(typeof(DescriptionAttribute), false);
            if (attrs.Length > 0)
            {
                for (int i = 0; i < attrs.Length; i++)
                {
                    string description = ((DescriptionAttribute) attrs[i]).Description;
                    if (text.Equals(description, StringComparison.OrdinalIgnoreCase))
                    {
                        return (T) Enum.Parse(type, member.Name, true);
                    }
                }
            }

            if (member.Name.Equals(text, StringComparison.OrdinalIgnoreCase))
            {
                return (T) Enum.Parse(type, member.Name, true);
            }
        }

        return default;
    }

    public static IEnumerable<string> GetDescriptions<T>() where T : struct, Enum
    {
        MemberInfo[] members = typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Static);
        foreach (MemberInfo member in members)
        {
            var attrs = member.GetCustomAttributes(typeof(DescriptionAttribute), false);
            if (attrs.Length > 0)
            {
                for (int i = 0; i < attrs.Length; i++)
                {
                    string description = ((DescriptionAttribute) attrs[i]).Description;

                    yield return description;
                }
            }
            else
            {
                yield return member.Name;
            }
        }
    }
}

And, I have a custom System.Text.Json converter:

public class EnumDescriptionConverterFactory : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum;

    public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) =>
        (JsonConverter?) Activator.CreateInstance(
            typeof(EnumConverterInner<>).MakeGenericType(new Type[] {typeToConvert}),
            BindingFlags.Instance | BindingFlags.Public,
            binder: null,
            args: null,
            culture: null);

    private class EnumConverterInner<T> : JsonConverter<T> where T : struct, Enum
    {
        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            string? propertyName = reader.GetString();

            MemberInfo[] members = typeToConvert.GetMembers(BindingFlags.Public | BindingFlags.Static);
            foreach (MemberInfo member in members)
            {
                var attrs = member.GetCustomAttributes(typeof(DescriptionAttribute), false);
                if (attrs.Length > 0)
                {
                    foreach (var t in attrs)
                    {
                        string description = ((DescriptionAttribute) t).Description;
                        if (propertyName?.Equals(description, StringComparison.OrdinalIgnoreCase) ?? false)
                        {
                            return (T) Enum.Parse(typeToConvert, member.Name, true);
                        }
                    }
                }

                if (member.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase))
                {
                    return (T) Enum.Parse(typeToConvert, member.Name, true);
                }
            }

            if (!Enum.TryParse(propertyName, false, out T result) &&
                !Enum.TryParse(propertyName, true, out result))
            {
                throw new JsonException(
                    $"Unable to convert \"{propertyName}\" to Enum \"{typeToConvert}\".");
            }

            return result;
        }

        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            Type type = value.GetType();

            var memberInfo = type.GetMember(value.ToString());
            if (memberInfo.Length > 0)
            {
                var attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

                if (attrs.Length > 0)
                {
                    var description = ((DescriptionAttribute) attrs[0]).Description;
                    writer.WriteStringValue(options.PropertyNamingPolicy?.ConvertName(description) ?? description);
                }
                else
                {
                    writer.WriteStringValue(options.PropertyNamingPolicy?.ConvertName(value.ToString()) ??
                                            value.ToString());
                }
            }
        }
    }

This code all works perfectly in every way I could test it, but when I plug it into a netcoreapp3.1 web api application, it converts the description to all lowercase in the output. In my tests, I get "Some of it" and in actual output from an API endpoint, I get "some of it"

In my ConfigureServices I have:

            services.AddControllers(options =>
                options.Filters.Add(new HttpResponseExceptionFilter())).AddJsonOptions(GetJsonOptions());

and the function being called is:

        private static Action<JsonOptions> GetJsonOptions()
        {
            return options =>
            {
                options.JsonSerializerOptions.IgnoreNullValues = true;
                options.JsonSerializerOptions.MaxDepth = 64;
                options.JsonSerializerOptions.Converters.Add(new EnumDescriptionConverterFactory());
            };
        }

As I wrote, I'm sure it's "working" because the transformation takes place, but the output is always lower case.

Anyone know why this might be happening or what I could do about it?

dbc
  • 104,963
  • 20
  • 228
  • 340
Vic F
  • 1,143
  • 1
  • 11
  • 26
  • 1
    Just a guess, it's probably because `options.JsonSerializerOptions.PropertyNamingPolicy` is initialized to `JsonNamingPolicy.CamelCase` on your server but not in your test harness. If you want to ignore the naming policy when the description is present, replace `writer.WriteStringValue(options.PropertyNamingPolicy?.ConvertName(description) ?? description)` with `writer.WriteStringValue(description);` – dbc Jan 29 '22 at 21:39
  • Does that answer your question? Your post lacks a [mcve] so I wasn't sure. – dbc Jan 30 '22 at 16:12
  • As dbc said, it is better to create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example), or, can you post the relates code in the API method and how to use the custom JSON converter? In the API method, I assume you are return the object result, right? If that is the case, you could try to convert the object to json string first, then return the json string to client. – Zhi Lv Feb 01 '22 at 08:09

0 Answers0