Nested validation

Validating DTOs inside one another.

To validate an entity, the underlying rules must be followed:

  • The entity must be an object

  • The object must have a single field, the id

  • The id must be a positive integer

Sounds familiar? We already have a structure that meets these requirements: The IdDto. Because of that, we can use it to type the categories field inside the DTO.

Apart from that, we just need to indicate that the validation used will be from the IdDto itself. We can achieve that with the @ValidateNested() decorator. Lastly, as it is not a primitive type, we need to explicitly state the type that is being validated with @Type(). With that, we should get the following result:

@ArrayNotEmpty()
@ArrayUnique()
@ValidateNested()
@Type(() => IdDto)
readonly categories: IdDto[];

As a last improvement, we can create the decorator @IsEntity() for this specific validation. Let's do so now in common/decorators/is-entity.decorator. Note that, in this case, we need to also add the validator @IsDefined() to enforce validation if the field is not present.

/** Checks if the value is an object with only a serial id. */
export const IsEntity = (): PropertyDecorator =>
  applyDecorators(
    IsDefined(),
    ValidateNested(),
    Type(() => IdDto),
  );

Now, we can use it in this field and whenever necessary.

However, the @ArrayUnique() validator will not work now because we are validating objects, which are compared by reference. Fortunately, we can fix this by passing an identifier function to the decorator that states how these objects should be compared. The idDtos should be compared by their ids, so let's create this function in common/util/id.util.

export const idDtoIdentifier = (dto: IdDto) => dto.id;

And then simply pass this function (not call it) to the decorator.

Commit - Implementing product logic and using nested validation

Last updated