DEV Community

garicchi
garicchi

Posted on

1 1 1

ASP.Net+Swagger+TypeScriptでEnumの表示名を自動生成する

ASP.NetはSwaggerをサポートしており、Controllerを書くだけでopenapi schemaが自動生成されて便利です。

そして自動生成されたopenapi schemaから、openapi-generatorなどのツールを利用して、TypeScriptのコードを自動生成すれば、Single Page Applicationが作りやすくなります。

レスポンスのPayloadにEnum型があった場合

しかし、レスポンスのPayloadにEnum型のプロパティがあった場合、あまり良いコードが得られません。

例えば、こんなC#のenumがあったとして、

public enum WeatherType
{
    Sunny = 0,
    Cloudy = 1,
    Rainy = 2,
}
Enter fullscreen mode Exit fullscreen mode

普通にopenapi-generatorで自動生成すると、以下のTypeScriptのコードが得られます。

export const WeatherType = {
    NUMBER_0: 0,
    NUMBER_1: 1,
    NUMBER_2: 2
} as const;
export type WeatherType = typeof WeatherType[keyof typeof WeatherType];
Enter fullscreen mode Exit fullscreen mode

EnumのメンバーがNUMBER_{数字} になってわかりにくいです。

x-enum-varnames

メンバーのキー名を変更するには、openapi schemaにx-enum-varnamesという名前で配列を入れてやればよいです。
https://github.com/OpenAPITools/openapi-generator/issues/893#issuecomment-416617460

ASP.NetのSwaggerジェネレータ (今回はSwashbuckle)の場合、SchemaFilterという機能があり、これを使えば、出力されるopenapi schemaをいじることができます。

こんな感じのSchemaFilterを用意して

public class EnumSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema model, SchemaFilterContext context)
    {
        if (context.Type.IsEnum)
        {
            var varNamesArray = new OpenApiArray();
            foreach (var memberVal in Enum.GetValues(context.Type))
            {
                var memberName = Enum.GetName(context.Type, memberVal);
                varNamesArray.Add(new OpenApiString(memberName));
            }

            model.Extensions.Add("x-enum-varnames", varNamesArray);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

AddSwaggerGenで指定してやります

builder.Services.AddSwaggerGen(options =>
{
    options.SchemaFilter<EnumSchemaFilter>();
});
Enter fullscreen mode Exit fullscreen mode

すると、openapi schemaにはx-enum-varnamesにメンバー名が入るようになります。

"WeatherType": {
  "enum": [
    0,
    1,
    2
  ],
  "type": "integer",
  "format": "int32",
  "x-enum-varnames": [
    "Sunny",
    "Cloudy",
    "Rainy"
  ]
}
Enter fullscreen mode Exit fullscreen mode

これを使ってTypeScriptを生成すると、キーが正しくメンバー名になります。

export const WeatherType = {
    Sunny: 0,
    Cloudy: 1,
    Rainy: 2
} as const;
export type WeatherType = typeof WeatherType[keyof typeof WeatherType];
Enter fullscreen mode Exit fullscreen mode

表示名を変えたい

x-enum-varnamesを使えば、TypeScriptのオブジェクトのキーとして、Enumのメンバー名を使用できましたが、
実際にUIに値を表示したい時は、もっと違う文字列を表示したいかもしれません。

例えばメンバー名を Sunny= とした場合、= はTypeScriptのオブジェクトのキーとして使えないのでエラーになります。

また、各国の言語に翻訳した名前を表示したいかもしれません。

EnumのAttributeを表示する

そこで、EnumにAttributeをつけて、それを表示するようにしてみます。
今回は System.ComponentModel.DataAnnotations.DisplayAttribute を使用します。

public enum WeatherType
{
    [Display(Name = "晴れ")]
    Sunny = 0,

    [Display(Name = "曇り")]
    Cloudy = 1,

    [Display(Name = "雨")]
    Rainy = 2,
}
Enter fullscreen mode Exit fullscreen mode

openapi schemaのテキトーなキーに、この表示名をつけれればいいので、
EnumSchmeFilterを以下のように修正し、 x-enum-displays というスキーマを追加してみます。

public class EnumSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema model, SchemaFilterContext context)
    {
        if (context.Type.IsEnum)
        {
            var varNamesArray = new OpenApiArray();
+           var displayArray = new OpenApiArray();
            foreach (var memberVal in Enum.GetValues(context.Type))
            {
                var memberName = Enum.GetName(context.Type, memberVal);
                varNamesArray.Add(new OpenApiString(memberName));
+               var obj = new OpenApiObject();
+               var display = (DisplayAttribute?)memberVal.GetType().GetMember(memberVal.ToString() ?? "").FirstOrDefault()?.GetCustomAttributes(typeof(DisplayAttribute), true).FirstOrDefault();
+               obj["value"] = new OpenApiInteger(Convert.ToInt32(memberVal));
+               obj["display"] = new OpenApiString(display?.Name);
+               displayArray.Add(obj);
            }

            model.Extensions.Add("x-enum-varnames", varNamesArray);
+           model.Extensions.Add("x-enum-displays", displayArray);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

これで生成されたスキーマは以下のようになります。

"WeatherType": {
  "enum": [
    0,
    1,
    2
  ],
  "type": "integer",
  "format": "int32",
  "x-enum-varnames": [
    "Sunny",
    "Cloudy",
    "Rainy"
  ],
  "x-enum-displays": [
    {
      "value": 0,
      "display": "晴れ"
    },
    {
      "value": 1,
      "display": "曇り"
    },
    {
      "value": 2,
      "display": "雨"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

あとはこれを使って、TypeScriptコードを生成してやればよいのですが、標準では今回作った x-enum-displays を読めないので、コード生成テンプレートを修正してやります。

テンプレートはここにあるので落としてきます
https://github.com/OpenAPITools/openapi-generator/tree/master/modules/openapi-generator/src/main/resources/typescript-fetch

次に、テンプレートの中にある modelEnumInterfaces.mustache に以下を追加します。

/*
customized
*/
{{^stringEnums}}
{{#vendorExtensions}}
export function Get{{classname}}DisplayName(v: {{classname}}) {
    switch(v) {
{{#x-enum-displays}}
        case {{value}}:
            return "{{display}}";
{{/x-enum-displays}}
        default:
            throw new Error(`value ${v} is not supported`);
    }
}
{{/vendorExtensions}}
{{/stringEnums}}
Enter fullscreen mode Exit fullscreen mode

あとはopenapi generatorで生成する時に、 -t オプションで、先ほど修正したテンプレートのディレクトリを指定します。

テンプレートのカスタマイズはこのドキュメントを参考にしてください。

これで、生成されたTypeScriptには、enumから表示名を取得する関数が追加されます。

export const WeatherType = {
    Sunny: 0,
    Cloudy: 1,
    Rainy: 2
} as const;
export type WeatherType = typeof WeatherType[keyof typeof WeatherType];

/*
customized
*/
export function GetWeatherTypeDisplayName(v: WeatherType) {
    switch(v) {
        case 0:
            return "晴れ";
        case 1:
            return "曇り";
        case 2:
            return "";
        default:
            throw new Error(`value ${v} is not supported`);
    }
}
Enter fullscreen mode Exit fullscreen mode

こんな感じで表示名を取得できます。

// 晴れ が返される
GetWeatherTypeDisplayName(WeatherType.Sunny)
Enter fullscreen mode Exit fullscreen mode

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (0)

Image of Datadog

Master Mobile Monitoring for iOS Apps

Monitor your app’s health with real-time insights into crash-free rates, start times, and more. Optimize performance and prevent user churn by addressing critical issues like app hangs, and ANRs. Learn how to keep your iOS app running smoothly across all devices by downloading this eBook.

Get The eBook