Deep Dive into Nested Object Validation in NestJS: Solutions Based on class-validator

Dec 06, 2025 · Programming · 7 views · 7.8

Keywords: NestJS | class-validator | nested object validation

Abstract: This article explores common challenges in validating nested objects using class-validator in the NestJS framework, particularly focusing on limitations with array validation. By analyzing a bug highlighted in a GitHub issue, it explains why validation may fail when inputs are primitive types or arrays instead of objects. Based on best practices, we provide a complete implementation of a custom validation decorator, IsNonPrimitiveArray, and demonstrate how to integrate it with @ValidateNested and @Type decorators to ensure proper validation of nested arrays. Additionally, the article discusses the role of class-transformer, uses code examples to illustrate how to avoid common pitfalls, and offers a reliable validation strategy for developers.

Challenges and Background of Nested Object Validation

In modern web development, data validation is crucial for ensuring application robustness. NestJS, a popular Node.js framework, often integrates with the class-validator library to implement declarative validation logic. However, when dealing with nested objects, especially arrays of objects, developers may encounter validation failures. For instance, when validating a property of type PositionDto[], if the input is [1] (an array of numbers), the system might incorrectly accept it instead of throwing an expected 400 error. This stems from a known bug in the class-validator library, where validation logic fails when inputs are primitive types (e.g., booleans, strings, numbers) or arrays rather than objects.

Root Cause Analysis

According to discussions in GitHub issues, class-validator has limitations in handling nested validation. When using the @ValidateNested({ each: true }) decorator to validate each element in an array, if the array contains non-object elements, the validator may not correctly identify type mismatches. This is because @ValidateNested relies on the @Type decorator from class-transformer for type transformation, but in some edge cases, the transformation logic may not catch all invalid inputs. For example, in the provided DTO, the positions property is defined as PositionDto[], but input [1] is treated as valid, even though it does not conform to the object structure of PositionDto.

Implementation of a Custom Validation Decorator

To address this issue, we can create a custom validation decorator, IsNonPrimitiveArray. This decorator checks that each element in the array is a non-primitive object, ensuring the input matches the expected structure for nested objects. Here is the implementation code:

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

export function IsNonPrimitiveArray(validationOptions?: ValidationOptions) {
  return (object: any, propertyName: string) => {
    registerDecorator({
      name: 'IsNonPrimitiveArray',
      target: object.constructor,
      propertyName,
      constraints: [],
      options: validationOptions,
      validator: {
        validate(value: any, args: ValidationArguments) {
          return Array.isArray(value) && value.reduce((a, b) => a && typeof b === 'object' && !Array.isArray(b), true);
        },
      },
    });
  };
}

This decorator uses Array.isArray to verify if the value is an array and employs a reduce method to ensure each element is an object and not an array. This compensates for the shortcomings of @ValidateNested, providing stricter validation.

Applying the Custom Decorator in DTOs

Integrating the IsNonPrimitiveArray decorator with existing validation logic enhances the validation of nested arrays. In the example DTO, we update the positions property as follows:

class PositionDto {
  @IsNumber()
  cost: number;

  @IsNumber()
  quantity: number;
}

export class FreeAgentsCreateEventDto {
  @IsNumber()
  eventId: number;

  @IsEnum(FinderGamesSkillLevel)
  skillLevel: FinderGamesSkillLevel;

  @ValidateNested({ each: true })
  @IsNonPrimitiveArray()
  @Type(() => PositionDto)
  positions: PositionDto[];
}

Here, @IsNonPrimitiveArray() ensures that the positions array contains only object elements, while @ValidateNested({ each: true }) and @Type(() => PositionDto) handle internal validation and type transformation for each object. This combination provides multiple layers of protection, effectively preventing invalid inputs.

Role of class-transformer and Best Practices

The @Type decorator from the class-transformer library plays a key role in nested validation by transforming input data into instances of specified classes, enabling class-validator to apply validation rules. In NestJS, this is typically enabled through a global validation pipe, as shown:

async function bootstrap() {
  const app = await NestFactory.create(ServerModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(config.PORT);
}
bootstrap();

The validation pipe automatically invokes class-transformer for transformation, but developers must ensure proper use of the @Type decorator in DTOs. For arrays, @Type(() => PositionDto) instructs the transformer to map each array element to a PositionDto instance. However, as noted, this may fail when inputs are primitive types, so combining it with custom decorators is recommended.

Supplementary References and Alternative Solutions

Beyond custom decorators, other answers offer useful insights. For example, some developers resolve issues by explicitly importing Type and ensuring correct decorator order. In complex scenarios, such as validating multi-language objects, using @IsObject() and @IsNotEmptyObject() can further refine validation. But the core issue remains the bug in class-validator, making custom solutions more reliable. Developers should regularly check library updates, as future versions may fix this problem.

Conclusion and Recommendations

When validating nested objects with class-validator in NestJS, special attention is needed for array validation. By implementing the IsNonPrimitiveArray custom decorator and combining it with @ValidateNested and @Type, robust validation logic can be built. It is advisable to test edge cases in real projects and consider encapsulating custom decorators into shared modules for better code reusability. As the class-validator library evolves, monitoring official fixes and adjusting implementations accordingly will help maintain application security and stability.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.