使用类验证器验证每个映射<字符串、数字>值



我正在尝试对JSON输入执行简单的验证,由我的一个dto建模。其中一个对象属性的类型是Map<string, number>。输入示例:

{
"type": "CUSTOM",
"is_active": true,
"current_plan_day": 1,
"custom_warmup_plan": {
"1": 123,
"2": 456
}

在我的控制器上,我使用DTO来指定主体类型。这个类,连同类验证器装饰符是这样的:

export class CreateWarmupPlanRequestDto {
@IsEnum(WarmupPlanType)
type: string;

@IsOptional()
@IsNumber({ allowInfinity: false, allowNaN: false, maxDecimalPlaces: 0 })
@IsPositive()
hard_cap: number | null;

@IsBoolean()
is_active: boolean;

@IsNumber({ allowInfinity: false, allowNaN: false, maxDecimalPlaces: 0 })
@IsPositive()
current_plan_day: number;

@IsOptional()
@IsNumber({ allowInfinity: false, allowNaN: false, maxDecimalPlaces: 0 })
@IsPositive()
previous_plan_day: number | null;

@IsOptional()
@IsNumber({ allowInfinity: false, allowNaN: false, maxDecimalPlaces: 0 }, { each: true })
@IsPositive({ each: true })
custom_warmup_plan: Map<string, number>;  // PROBLEM HERE
}

我希望验证custom_warmup_plan的每个值是一个现有的正整数。对象的其他属性的验证工作正常,如预期的那样,但对于我的示例输入,我不断得到错误(2个错误消息,连接):

{
"message": "each value in custom_warmup_plan must be a positive number. |#| each value in custom_warmup_plan must be a number conforming to the specified constraints",
"statusCode": 400,
"timestamp": "2021-07-29T13:18:29.331Z",
"path": "/api/warmup-plan/bc4c3f0e-8e77-46de-a46a-a908edbdded5"
}

这似乎是相当直接的文档,但我只是不能得到它的工作。我也玩了一个简单的Map<string, string>@IsString(each: true)验证器,但这似乎也不起作用。

任何想法?

版本:

"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
"@nestjs/mapped-types": "^1.0.0",
"@nestjs/platform-express": "^8.0.0",
"class-transformer": "^0.4.0",
"class-validator": "^0.13.1",

需要将普通对象转换为映射。在class-transformer

中使用Transform装饰器
@IsOptional()
@IsNumber(undefined, { each: true })
@Transform(({ value }) => new Map(Object.entries(value)))
prop?: Map<string, number>;

From the docs

如果你的字段是一个数组,并且你想对数组中的每个项执行验证,你必须指定一个特殊的each: true装饰器选项

如果你想能够验证映射,你可以编写一个自定义装饰器,并传入一个class-validator函数列表来验证键和值。例如,下面的装饰器将键和值的验证函数列表作为输入(例如传入isString,isObject等)。,class-validator有一个相应的函数,你可以调用它们提供的所有验证装饰器)

export function IsMap(
key_validators: ((value: unknown) => boolean)[],
value_validators: ((value: unknown) => boolean)[],
validationOptions?: ValidationOptions
) {
return function (object: unknown, propertyName: string) {
registerDecorator({
name: 'isMap',
target: (object as any).constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: unknown, args: ValidationArguments) {
if (!isObject(value)) return false;
const keys = Object.keys(value);
const is_invalid = keys.some((key) => {
const is_key_invalid = key_validators.some((validator) => !validator(key));
if (is_key_invalid) return false;
const is_value_invalid = value_validators.some((validator) => !validator(value[key]));
return is_value_invalid;
});
return !is_invalid;
},
},
});
};
}

你可以在你的例子中像这样使用这个装饰符

import { isInt } from 'class-validator'
export class CreateWarmupPlanRequestDto {
@IsOptional()
@IsMap([], [isInt])
custom_warmup_plan: Map<string, number>;
}

使用与@Daniel相同的方法,我稍微修改了代码,以便重点放在'isValid'而不是'IsInvalid'上。这样我们就可以避免双重否定。此外,即将到来的对象被转换为DTO中的映射。

@Transform(({ value }) => new Map(Object.entries(value)))
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
} from 'class-validator';
import * as $$ from 'lodash';
export function IsMap(
keyValidators: ((value: unknown) => boolean)[],
valueValidators: ((value: unknown) => boolean)[],
validationOptions?: ValidationOptions,
) {
return function (object: unknown, propertyName: string) {
/**
* ** value is expected to be a MAP already, we are just checking types of keys and values...
*/
registerDecorator({
name: 'isMap',
target: (object as any).constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: Map<any, any>, args: ValidationArguments) {
if (!$$.isMap(value)) {
return false;
}
const keys = Array.from(value.keys());
return $$.every(keys, (key) => {
// checking if keys are valid...
const isKeyInvalid = keyValidators.some(
(validator) => !validator(key),
);
if (isKeyInvalid) {
return false;
}
// checking if values are valid...
const isValueInvalid = valueValidators.some(
(validator) => !validator(value.get(key)),
);
if (isValueInvalid) {
return false;
} else {
return true;
}
});
},
},
});
};
}

相关内容

  • 没有找到相关文章

最新更新