swagger
swagger copied to clipboard
Numeric enum values not displayed in Swagger
Is there an existing issue for this?
- [X] I have searched the existing issues
Current behavior
Using enums with number values results in incorrect Swagger examples. For example
export enum Size {
SMALL = 1,
BIG = 2,
}
export class TshirtDto {
@IsEnum(Size)
size: Size;
}
results in the following example:
{
"size": "SMALL"
}
which is wrong and invalid.
Minimum reproduction code
https://github.com/petrzjunior/nestjs-swagger-enum
Steps to reproduce
- Clone the above repo
pnpm ipnpm start:dev- Open http://localhost:3000/api/#/default/AppController_getHello
- See the example value for the only endpoint
Expected behavior
I expect Swagger to show the actual numeric value which will pass the validation. In this case
{
"size": 1
}
Package version
5.2.1
NestJS version
8.0.0
Node.js version
16.9.1
In which operating systems have you tested?
- [ ] macOS
- [ ] Windows
- [X] Linux
Other
I suspect the culprit is this loop over Enum values: https://github.com/nestjs/swagger/blob/491b168cbff3003191e55ee96e77e69d8c1deb66/lib/utils/enum.utils.ts#L17
In JS, number keys are iterated before the string ones. The string values (from reverse mapping) are added to the list first, later ignoring the correct numeric keys.
node> for (const key in {a: 1, 1: 'a'}) { console.log(key) }
1
a

but if we enable the builtin auto-validation, since we're using @IsEnum(Size), requesting with
{ "size": 1 }
will give us the following validation error:
{
"statusCode": 400,
"message": [
"size must be a valid enum value"
],
"error": "Bad Request"
}
I didn't follow what's the issue here :thinking:
EDIT:
nvm. I got it now. The reponse object will always have the value of an entry from that enum, not the key
@micalevisk yeah not only the response, but also the request body.
No workaround or fix yet?
I use this workaround for numeric enums:
Workaround Source
// options for custom decorator
type ApiPropertyEnumOptions<T extends { [key: string]: any; }> = {
/**
* Indicates if multiple enum values can be used as the same time (thus being an array).
* Defaults to `false`.
*/
isArray?: boolean;
/**
* The enum that should be represented.
*/
enum: T;
/**
* A unique name for the enum. All enums that are the same should use the same enumName.
* It's sadly not possible to autogenertate since enums are not part of the JS ecosystem.
*/
enumName: string;
/**
* The type of the enum values. Defaults to `'number'`.
*/
type?: 'string' | 'number';
};
// util method that retrieves only "real" values from an enum
function getEnumValues<T extends { [key: string]: any; }>(value: T): T[keyof T][] {
return (Object.keys(value) as (keyof T)[])
.filter((key): key is keyof T => isNaN(parseInt(key.toString(), 10)))
.map((key) => value[key]);
}
// create a custom decorator for enum values
function ApiPropertyEnum<T extends { [key: string]: any; }>(options: ApiPropertyEnumOptions<T>): PropertyDecorator {
const {
enum: enumRef,
enumName,
isArray = false,
type = 'number',
} = options;
const enumValues = getEnumValues(enumRef);
const exampleValue = enumValues[0];
return applyDecorators(
ApiProperty({
enum: enumValues ,
enumName,
isArray,
type,
example: isArray ? [exampleValue] : exampleValue,
}),
);
}
Usage example
enum MyEnum {
FOO = 1,
BAR = 2,
BAZ = 3,
}
class MyDto {
@ApiPropertyEnum({
enum: MyEnum,
enumName: 'MyEnum',
})
value: MyEnum;
}
@Controller()
class MyController {
@Post()
getBody(
@Body()
body: MyDto,
) {
return body;
}
}
If MyController is now passed into a module that is correctly set up in a nest factory (including Swagger), you will get a numeric enum component schema in your Swagger UI.

Just in case someone needs it.
I also provided a repository where you can try it for yourself to verify that it works.
How can I achieve this effect? Use the Key of the enumeration as the display text of the Option and the Value as the value of the Option.
enum Role {
Admin = 1,
User = 2,
}
In Swagger UI:

How can I achieve this effect?
The Swagger specification does not support this behavior. You might be interested in this comment from the swagger-ui repository. Thats the closed you can get without a custom parse/builder.
Let's track this here https://github.com/nestjs/swagger/pull/1898