'How to serialize enum as string in Function App running on .net5 with OpenAPI/Swagger?

I am exploring Function App running on .net5 in the new isolated mode. I have HTTP triggered functions that I want to advertise via OpenAPI / Swagger.

To do so, I am using the package Microsoft.Azure.WebJobs.Extensions.OpenApi in preview (0.7.2) to add the OpenAPI functionality to my Function App.

I am trying to have the enums to be shown as string in the OpenAPI page but I can't have it working properly.

Here is the setup in the Program.cs file:

    public static class Program
    {
        private static Task Main(string[] args)
        {
            IHost host = new HostBuilder()
                .ConfigureAppConfiguration(configurationBuilder =>
                {
                    configurationBuilder.AddCommandLine(args);
                })
                .ConfigureFunctionsWorkerDefaults(builder =>
                {
                    builder.Services.Configure<JsonSerializerOptions>(options =>
                    {
                        options.Converters.Add(new JsonStringEnumConverter());
                        options.PropertyNameCaseInsensitive = true;
                    });
                })
                .ConfigureServices(services =>
                {
                    //  Registers any services.             
                })
                .Build();

            return host.RunAsync();
        }
    }

Here is the enum:

    [JsonConverter(typeof(JsonStringEnumConverter))]
    public enum ApprovalContract
    {
        [EnumMember(Value = "Approved")]
        Approved = 1,

        [EnumMember(Value = "Rejected")]
        Rejected = 2
    }

And one of the class that uses it:

    public sealed class DeletionResponseContract
    {
        [JsonPropertyName("approval")]
        public ApprovalContract Approval { get; set; }
    }

I replaced any references to Newtonsoft.Json by System.Text.Json everywhere.

Here is the output in the Swagger page:

Enum in the Swagger page

Question

How can I serialize enum as string instead of int in the Swagger page with an HTTP triggered Azure Function running on .net5?

Update

I saw that the JsonStringEnumConverter's constructor gives the indication to allow integer values:

     public JsonStringEnumConverter(JsonNamingPolicy? namingPolicy = null, bool allowIntegerValues = true)
    {
      this._namingPolicy = namingPolicy;
      this._converterOptions = allowIntegerValues ? EnumConverterOptions.AllowStrings | EnumConverterOptions.AllowNumbers : EnumConverterOptions.AllowStrings;
    }

I modified my configuration like this, without any success:

builder.Services.Configure<JsonSerializerOptions>(options =>
{
     options.Converters.Add(new JsonStringEnumConverter(allowIntegerValues: false));
     options.PropertyNameCaseInsensitive = true;
});


Solution 1:[1]

You must implemente ISchemaFilter and set it on AddSwaggerGen. It will generate a better description of your enum items.

builder.Services.AddSwaggerGen(c =>
{
    c.SchemaFilter<EnumSchemaFilter>();
});


//your implementation
public class EnumSchemaFilter : ISchemaFilter
    {
        public void Apply(OpenApiSchema model, SchemaFilterContext context)
        {
            if (context.Type.IsEnum)
            {
                model.Enum.Clear();
                Enum.GetNames(context.Type)
                    .ToList()
                    .ForEach(name => model.Enum.Add(new OpenApiString($"{Convert.ToInt64(Enum.Parse(context.Type, name))} - {name}")));
            }
        }
    }

Solution 2:[2]

I was inspired by Murilo's answer, but couldn't get it to work. So here is an alternative solution:

Create a document filter that will:

  • find all the enum properties in you swagger schemas

  • then find the matching property in your code using reflection (note: this doesn't take account of namespaces, so if you have multiple classes with the same name it could fail)

  • update the swagger property with the values from the c# enum

     public class EnumDocumentFilter : IDocumentFilter
     {
         public void Apply(IHttpRequestDataObject req, OpenApiDocument document)
         {
             foreach(var schema in document.Components.Schemas)
                 foreach(var property in schema.Value.Properties)
                     if (property.Value.Enum.Any())
                     {
                         var schemaType = Assembly.GetExecutingAssembly().GetTypes().Single(t => t.Name == Camel(schema.Key));
                         var propertyType = schemaType.GetProperty(Camel(property.Key)).PropertyType;
                         property.Value.Enum = Enum.GetNames(propertyType)
                             .Select(name => new OpenApiString(name))
                             .Cast<IOpenApiAny>()
                             .ToList();
                         property.Value.Type = "string";
                         property.Value.Default = property.Value.Enum.First();
                         property.Value.Format = null;
                     }
         }
    
         private static string Camel(string key)
             => $"{char.ToUpperInvariant(key[0])}{key[1..]}";
     }
    

Then register that filter in your OpenApiConfigurationOptions

    public class OpenApiConfigurationOptions : DefaultOpenApiConfigurationOptions
    {
...
        public override List<IDocumentFilter> DocumentFilters { get => base.DocumentFilters.Append(new EnumDocumentFilter()).ToList(); }
    }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Murilo Maciel Curti
Solution 2 Alan Hinton