3

I'm working with TypeORM, and I would like to provide different to field based on other field value. So to explain, here is how my DTO model looks:

import { IsString, IsOptional, IsNumber, IsEnum, IsObject, IsBoolean, ValidateNested } from 'class-validator';

export enum AttributeTypes {
    DATE = 'DATE',
    TIME = 'TIME',
    NUMBERS = 'NUMBERS',
}

export class BaseValidation {
    @IsOptional()
    @IsBoolean()
    required: boolean;
}

export class modelCreate {
    @IsOptional()
    @IsNumber()
    id: number;

    @IsOptional()
    @IsString()
    label: string;

    @IsOptional()
    @IsEnum(AttributeTypes)
    type: AttributeTypes;

    @IsOptional()
    @IsObject()
    @ValidateNested()
    validation: BaseValidation;
}

The problem here is that I have this field: validation in modelCreate, & that field is an object and can have multiple properties & can look like this in db:

validation: {
   required: true,
   text: 2
}

or it can look like this:

   validation: {
       required: false,
       number: 1,
       maxNumber: 10
    }

and that would depend on type property of modelCreate, because if type is 'TIME', I would like to have validation for this:

BaseValidation {
    @IsBoolean()
     required: true,
    @IsString()
    text: 2
}

and if type is 'NUMBERS', I would like to have validation like this

  BaseValidation {
           @IsBoolean()
           required: boolean,
           @IsNumber()
           number: number,
           @IsNumber()
           maxNumber: number
        }

So the question is how would I toogle different classes in validation field based on type field value in class validator, and is that even possible ?

2 Answers 2

3

for that you need to create 2 classes, for every case, string and numbers. Then change the type validation: StringBaseValidation | NumberBaseValidation; now class-validator can distinguish the cases.

If the data comes from a request you need to use @Type((obj) => obj.writeConditionToDetectString ? StringBaseValidation : NumberBaseValidation) to help the lib to understand which type is the data from the request.

Sign up to request clarification or add additional context in comments.

Comments

2

You should have a discriminative field, to allow the class-transformer to identify the type to cast and use in the validation. The same can be done with nested array objects. In your case you can use the "required" field

In the example below, if the property required is equal to true, then we use the class A, in the other hand, if the property required is equal to false the class to be used is B.
In the discriminator property you should set a field to be used to identify the different polymorphic forms of the nested objects. The subTypes.[].value is the class to be used in validation and subTypes.[].name is the value that the discriminator field should have to assume the class added in the subTypes.[].name field.

import { Type } from 'class-transformer';
import { IsBoolean, IsNumber, IsString, ValidateNested } from 'class-validator';

class Discriminator {
  @IsBoolean()
  required: boolean;
}

class A extends Discriminator {  
  @IsString()
  text: string;
}

class B extends Discriminator {  
  @IsNumber()
  number: number;

  @IsNumber()
  maxNumber: number
}

export class C {

  // Other fields...

  @ValidateNested()
  @Type(() => Discriminator, {
    discriminator: {
      property: 'required',
      subTypes: [
        { value: A, name: true as unknown as string },
        { value: B, name: false  as unknown as string },
      ],
    },
    keepDiscriminatorProperty: true,
  })
  validation: A | B;
}

Now your controller should look like this:

@Controller()
export class AppController {
  @Post()
  public example(
    @Body(new ValidationPipe({ transform: true }))
    body: C,
  ) {
    return body;
  }
}

And you can do your requests:

curl --location 'localhost:3000/' \
--header 'Content-Type: application/json' \
--data '{
  "validation": {
    "required": true,
    "text": "Some text..."
  } 
}'

If you change the second element of the required property to false, then you will see an error:

{
- "required": true,
+ "required": false,
  "text": "Some text..."
}
{
  "statusCode": 400,
  "message": [
    "validation.number must be a number conforming to the specified constraints",
    "validation.maxNumber must be a number conforming to the specified constraints"
  ],
  "error": "Bad Request"
}

References: https://github.com/typestack/class-transformer#providing-more-than-one-type-option

1 Comment

The workaround of the type is mentioned here: github.com/typestack/class-transformer/issues/737

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.