You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用class-validator验证Record<string, string[]>失败求助

问题:class-validator自定义IsRecord装饰器验证Record<string, string[]>类型无效

尝试用class-validator验证Record<string, string[]>类型数据,但自定义的IsRecord装饰器未生效。以下是相关代码:

自定义IsRecord装饰器代码

import {
  isObject,
  registerDecorator,
  ValidationArguments,
  ValidationOptions,
} from 'class-validator';

export const IsRecord = (
  key_validators: ((value: string) => boolean)[],
  value_validators: ((value: string[]) => boolean)[],
  validationOptions?: ValidationOptions,
) => {
  return function (object: unknown, propertyName: string) {
    registerDecorator({
      name: 'IsRecord',
      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);

          return keys.some(key => {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            const is_key_invalid = key_validators.some(
              validator => !validator(key),
            );
            if (is_key_invalid) return false;

            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            return value_validators.some(validator => !validator(value[key]));
          });
        },
      },
    });
  };
};

DTO定义代码

import {
  BaseRole,
  ModuleName,
  Topic,
} from 'my-package';
import { IsBoolean, IsEnum, IsObject, IsOptional } from 'class-validator';
import { IsRecord } from './decorators/is-record.decorator';

export class AutoRegisterDto {
  @IsEnum(ModuleName)
  name: ModuleName;

  @IsBoolean()
  isEnabled: boolean;

  @IsObject()
  @IsOptional()
  @IsRecord([], [])
  actions: Record<Topic, BaseRole[]>;
}

相关枚举与类型定义

BaseRole枚举

export declare enum BaseRole {
    UNREGISTERED_GUEST = "unregistered_user",
    UNVERIFIED_GUEST = "unverified_guest",
    VERIFIED_GUEST = "verified_guest",
    STUDENT = "student",
    EMPLOYEE = "employee",
    ADMIN = "admin"
}

Topic类型

export declare type Topic = string;

问题原因与修复方案

你的装饰器存在两个核心逻辑错误:

  1. 遍历逻辑颠倒:使用keys.some()会导致只要有一个键值对"符合条件"就返回true,这和"所有键值对都必须通过验证"的需求完全相反,应该替换为keys.every()。
  2. 验证判断逻辑错误:value_validators.some(validator => !validator(value[key]))的逻辑是"只要有一个验证器不通过就返回true",这会导致验证失败的键值对被误判为有效,需要改为验证所有验证器都通过。

修复后的IsRecord装饰器代码:

import {
  isObject,
  registerDecorator,
  ValidationArguments,
  ValidationOptions,
} from 'class-validator';

export const IsRecord = (
  key_validators: ((value: string) => boolean)[],
  value_validators: ((value: string[]) => boolean)[],
  validationOptions?: ValidationOptions,
) => {
  return function (object: unknown, propertyName: string) {
    registerDecorator({
      name: 'IsRecord',
      target: (object as any).constructor,
      propertyName: propertyName,
      options: validationOptions,
      validator: {
        validate(value: unknown, args: ValidationArguments) {
          if (!isObject(value)) return false;
          const entries = Object.entries(value);

          // 确保所有键值对都通过验证
          return entries.every(([key, val]) => {
            // 所有键验证器都必须通过
            const allKeysValid = key_validators.every(validator => validator(key));
            if (!allKeysValid) return false;

            // 所有值验证器都必须通过
            const allValuesValid = value_validators.every(validator => validator(val));
            return allValuesValid;
          });
        },
      },
    });
  };
};

另外,你当前调用@IsRecord([], [])时传入了空数组,意味着没有任何实际验证逻辑。如果要验证BaseRole[]中的元素都是有效枚举值,可以添加对应验证逻辑:

// 新增BaseRole数组验证函数
const isBaseRoleArray = (value: string[]): boolean => {
  return value.every(item => Object.values(BaseRole).includes(item));
};

// 在DTO中修改装饰器调用
@IsOptional()
@IsRecord([], [isBaseRoleArray])
actions: Record<Topic, BaseRole[]>;

内容的提问来源于stack exchange,提问作者Ibrahim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 19:40:29