3

I'd like to do a following body validation in my NestJs app:

a and b are optional properties if one of them is supplied, otherwise one of them should be required

payload: { a: 'some value', c: 'another value' } -> OK
payload: { b: 'some value', c: 'another value' } -> OK
payload: { a: 'some value1', b: 'some value2', c: 'another value' } -> OK
payload: { c: 'another value' } -> Error: either a or b should be present in the payload`

I have the following DTO:

class MyDto {
  @OneOfOptionalRequired(['a', 'b'])
  @ApiProperty()
  @IsString()
  @IsOptional()
  a: string

  @OneOfOptionalRequired(['a', 'b'])
  @ApiProperty()
  @IsString()
  @IsOptional()
  b: string

  @ApiProperty()
  @IsString()
  c: string
}

I've tried creating my own decorator which will perform described validation:

export function OneOfOptionalRequired(requiredFields: string[], validationOptions?: ValidationOptions) {
  return function (target: object, propertyName: string) {
    registerDecorator({
      name: 'oneOfOptionalRequired',
      target: target.constructor,
      propertyName: propertyName,
      options: {
        message: `Missing one of the required fields [${requiredFields}]`,
        ...validationOptions
      },
      validator: {
        validate(value: unknown, args: ValidationArguments) {
          const payload = args.object
          return requiredFields.some(x => has(payload, x))
        },
      },
    })
  }
}

But actually it does not work for the case when only c property is present in the payload, because @IsOptional() turns off all of the decorators. If I remove @IsOptional() for a and b then I will get the error saying that a and b should not be empty. So I'm kind of stuck here

2 Answers 2

2

You could make your own decorator, especially if this is a repeating pattern in your application or if you expect to need this replicated over more than two properties in the same DTO. However, for this specific case, you could solve it in a more straight-forward manner:

@ValidateIf(dto => typeof dto.b === 'undefined')
@IsString()
a?: string;

@ValidateIf(dto => typeof dto.a === 'undefined')
@IsString()
b?: string;

Of course, you can export and re-use this as following:

export const RequiredIfPropertyMissing = (property: string) => 
  ValidateIf(dto: any => typeof dto[property] === 'undefined');

Hope it helps, and good luck!

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

2 Comments

Thanks for your answer, but your proposed solution will not validate a and b properties, if both of them are supplied.
You're right! I missed that bit. Unfortunately class-validator is a pretty stale repo, but this PR suggests an implementation for passing a condition to IsOptional. You could replicate that implementation and use it as @IsOptional(o => o.b !== undefined)
1

You can expand the ValidateIf decorator to add when those two fields exists.

@ValidateIf(dto => !dto.b || (dto.a && dto.b))
@IsString()
a?: string;

@ValidateIf(dto => !dto.a || (dto.a && dto.b))
@IsString()
b?: string;

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.