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

class-validator校验Polygon三维坐标数组报错排查

三维坐标数组校验失败问题

请求载荷

{
  "name": {
    "ar": "مصر",
    "en": "EG"
  },
  "location": {
    "type": "Polygon",
    "coordinates": [
      [
        [30.7939, 26.8206],
        [32.1597, 26.8206],
        [32.1597, 23.6345],
        [30.7939, 23.6345],
        [30.7939, 26.8206]
      ]
    ]
  },
  "addressesType": "Country"
}

需求

coordinates需为三维数组,最内层每个数组恰好包含2个数字,分别代表经度和纬度。

当前校验代码

/* eslint-disable lines-between-class-members */
/* eslint-disable @darraghor/nestjs-typed/validated-non-primitive-property-needs-type-decorator */
import { ArrayMaxSize, ArrayMinSize, ArrayNotEmpty, Equals, IsArray, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';

export default class AddressLocationDto {
  @IsOptional()
  @IsString()
  @Equals('Polygon')
  type: string;

  @IsArray()
  @ArrayNotEmpty()
  @ArrayMinSize(1)
  @ArrayMaxSize(1)
  @ValidateNested({ each: true })
  @IsArray()
  @ArrayNotEmpty()
  @ArrayMinSize(1)
  @ValidateNested({ each: true })
  @IsArray()
  @ArrayNotEmpty()
  @ArrayMinSize(2)
  @ArrayMaxSize(2)
  @IsNumber({}, { each: true })
  coordinates: number[][][];
}

错误信息(中文翻译)

coordinates的每个元素必须是类实例或普通对象,但收到的是数组。

问题原因

直接在coordinates属性上叠加多层数组校验装饰器的方式错误,且滥用了@ValidateNested——该装饰器用于验证嵌套的对象/类实例,而非嵌套数组。装饰器会被依次应用于当前属性,而非逐层作用于数组的嵌套层级,导致校验逻辑混乱。

解决方案

方案1:嵌套DTO类(推荐,符合class-validator最佳实践)

通过拆分嵌套DTO类,逐层校验数组结构:

/* eslint-disable lines-between-class-members */
/* eslint-disable @darraghor/nestjs-typed/validated-non-primitive-property-needs-type-decorator */
import { ArrayMaxSize, ArrayMinSize, ArrayNotEmpty, Equals, IsArray, IsNumber, IsOptional, IsString, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';

// 最内层:经纬度数组校验
class CoordinateDto {
  @IsArray()
  @ArrayMinSize(2)
  @ArrayMaxSize(2)
  @IsNumber({}, { each: true })
  readonly value: number[];
}

// 中间层:多边形环的坐标点数组校验
class PolygonRingDto {
  @IsArray()
  @ArrayNotEmpty()
  @ValidateNested({ each: true })
  @Type(() => CoordinateDto)
  readonly coordinates: CoordinateDto[];
}

export default class AddressLocationDto {
  @IsOptional()
  @IsString()
  @Equals('Polygon')
  type: string;

  @IsArray()
  @ArrayNotEmpty()
  @ArrayMinSize(1)
  @ArrayMaxSize(1)
  @ValidateNested({ each: true })
  @Type(() => PolygonRingDto)
  coordinates: PolygonRingDto[];
}

方案2:自定义验证器(简洁高效)

编写自定义验证器直接校验三维数组结构,无需拆分嵌套类:

/* eslint-disable lines-between-class-members */
/* eslint-disable @darraghor/nestjs-typed/validated-non-primitive-property-needs-type-decorator */
import { ArrayMaxSize, ArrayMinSize, ArrayNotEmpty, Equals, IsArray, IsNumber, IsOptional, IsString, registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';

// 自定义验证器:校验多边形坐标的三维数组结构
function IsValidPolygonCoordinates(validationOptions?: ValidationOptions) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      name: 'isValidPolygonCoordinates',
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      validator: {
        validate(value: any) {
          // 第一层:必须是长度为1的数组
          if (!Array.isArray(value) || value.length !== 1) return false;
          const ring = value[0];
          // 第二层:必须是非空数组
          if (!Array.isArray(ring) || ring.length === 0) return false;
          // 最内层:每个元素必须是长度为2的数字数组
          return ring.every(point => 
            Array.isArray(point) && 
            point.length === 2 && 
            point.every(num => typeof num === 'number')
          );
        },
        defaultMessage() {
          return 'coordinates必须是三维数组,最内层每个数组需包含2个数字(经度、纬度)';
        }
      }
    });
  };
}

export default class AddressLocationDto {
  @IsOptional()
  @IsString()
  @Equals('Polygon')
  type: string;

  @IsValidPolygonCoordinates()
  coordinates: number[][][];
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 04:37:55