Search code examples
javascriptnestjsdecoratordto

Multikind Object validation in Nestjs DTO


I have code something like below. But I found out that @ValidateNested doesn't support when we do "TwoWheel | FourWheel" like this. Also I have no way of determining if incoming payload vehicle type is TwoWheel or FourWheel. I need to validate that as well. How can I do so ?

Please note that these are just dummy classes I have created. In reality, I have 3 classes and each have many fields. Only some of them are common. Most of them are not.

ChatGPT suggested to create custom validator. But I was hoping nestjs might have something inbuilt that I can use. Please suggest better approach.

Thanks in advance.

class TwoWheel{
    @IsNotEmpty()
    @IsNumber()
    speed: number;

    @IsNotEmpty()
    @IsString()
    color: string
}


class FourWheel{
    @IsNotEmpty()
    @IsNumber()
    speed: number;

    @IsNotEmpty()
    @IsString()
    color: string

    @IsNotEmpty()
    @IsNumber()
    numberOfSeats: number;
}

class Vehicle{
    
    @ValidateNested()
    VehicleType : TwoWheel | FourWheel
}

Another thing I tried was something like

    @IsEnum([TwoWheel, FourWheel],{
        message : "Invalid Vehicle Type"
    })
    VehicleType : TwoWheel | FourWheel

But above will always return error message. At least getting @ValidateNested would also be win for me. I need to validate nested field while doing "VehicleType : TwoWheel | FourWheel" Don't want too complex solution. Just want to find out if there is something in nestjs that I missed.


Solution

  • You can use the @Type annotation from class-transformer to manually check the type of the input based on a specific parameter. For example

    export class VehicleA {
      @IsNotEmpty()
      doors: number;
    }
    
    export class VehicleB {
      @IsNotEmpty()
      doors: number;
    
      @IsNotEmpty()
      seats: number;
    }
    
    export class MyDTO {
    
      @IsNotEmpty({
        message: "Vehicle should not be empty"
      })
      @ValidateNested({ each: true })
      @Type((options) => {
        if (options?.object && "seats" in options.object.vehicle) {
          return VehicleB;
        }
    
        return VehicleA;
      })
      vehicle: VehicleA | VehicleB;
    
    }
    

    In this scenario you can use the fact that VehicleB has a property seats and then return the appropriate type, almost like a typeguard.

    Edit: Adding here a better answer using the @Type discriminator: https://github.com/typestack/class-transformer?tab=readme-ov-file#providing-more-than-one-type-option