Repository navigation
Validating a nested object with a property from the parent #2074
Replies: 4 comments 1 reply
|
Hi, i have this problem too... the parent information is a relevant data that can't be ignored within a complex custom contraint. What should we do? |
|
Hi I'm also having this issue, just want to check if there's anyone that already have the solution ? |
|
The problem is @type()'s discriminator resolution happens before your nested class ever sees the parent object, so hasRedundancy genuinely isn't available there. I'd write a custom class-level @ValidatorConstraint on CreateStreamRequest itself that checks object.hasRedundancy && !object.source.secondary and fails there - validating the cross-field rule at the level where both properties actually exist, rather than reaching up from the nested class. |
|
The root-level constraint suggested above is the right direction. One implementation detail: class-validator validates properties, so attach the cross-field constraint to There is no need to inject parent references into the input or introduce a circular object graph. Use two separate rules:
Here is a complete DTO-only example retaining the discriminator: import 'reflect-metadata';
import { Type } from 'class-transformer';
import {
IsBoolean, IsDefined, IsIn, IsObject, IsOptional, IsString, IsUrl, Validate,
ValidateNested, ValidatorConstraint,
type ValidationArguments, type ValidatorConstraintInterface,
} from 'class-validator';
export class SourceDto {
@IsIn(['HLS', 'MP4'])
type!: 'HLS' | 'MP4';
@IsUrl()
primary!: string;
@IsOptional()
@IsUrl()
secondary?: string;
}
export class HLSSource extends SourceDto { declare type: 'HLS'; }
export class MP4Source extends SourceDto { declare type: 'MP4'; }
@ValidatorConstraint({ name: 'secondaryRequired', async: false })
export class SecondaryRequired implements ValidatorConstraintInterface {
validate(source: unknown, args: ValidationArguments): boolean {
const parent = args.object as CreateStreamRequest;
if (parent.hasRedundancy !== true) return true;
if (typeof source !== 'object' || source === null) return false;
const secondary = (source as { secondary?: unknown }).secondary;
return typeof secondary === 'string' && secondary.trim().length > 0;
}
defaultMessage(): string {
return 'source.secondary is required when hasRedundancy is true';
}
}
export class CreateStreamRequest {
@IsString()
name!: string;
@IsBoolean()
hasRedundancy!: boolean;
@IsDefined()
@IsObject()
@ValidateNested()
@Type(() => SourceDto, {
discriminator: { property: 'type', subTypes: [
{ name: 'HLS', value: HLSSource },
{ name: 'MP4', value: MP4Source },
] },
keepDiscriminatorProperty: true,
})
@Validate(SecondaryRequired)
source!: SourceDto;
}Apply it through your normal Nest pipe: import { ValidationPipe } from '@nestjs/common';
app.useGlobalPipes(new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: true,
}));I checked this with the real Nest 11.1.28
The relevant suite had five passing tests covering those cases. I did not enable implicit boolean conversion, so Also, your sample payload has References: custom constraints / ValidationArguments, conditional validation, and class-transformer discriminators. |
Uh oh!
There was an error while loading. Please reload this page.
There doesn't seem to be a straight forward way of validating a nested object based on a property from its parent or further up the tree. I'm looking for guidance on the best way to achieve this.
For example, I have the following object from a form:
{ "name": "My Video Stream", "hasRedundancy": true, "source": { "type": "hls", "primary": "https://some-domain.com/index.m3u8", "secondary": "https://some-domain.com/backup.m3u8" } }The
secondaryproperty in thesourceobject is only required ifhas_redundancyistrue. So I write my classes like so:But
hasRedundancyis not included in the plain object that the mySourceis created from, only in the top level plain object thatCreateStreamRequestis made from. I thought about using the groups feature to tackle this but that seems like double handling because I already have the data to pass to the condition in the original object. I see that the first parameter in theTypedecorator provides some parameters but they are not documented anywhere.All reactions