Generic class validation
Hello,
Is there any way to write validation on a generic class. Here an exemple of what I'd like to write :
export class ClassName<T> {
@IsNotEmpty()
@Expose()
@IsArray()
@ValidateNested({
each: true,
})
@Type(() => T)
public items: T[];
}
Do you have any idea ?
Thanks a lot for your answers :smiley:
Decorators are runtime artifact, hence why T doesn't exist.
You should try create a class factory:
export function createMyClass<T>(Type: T) {
class ClassName {
@IsNotEmpty()
@Expose()
@IsArray()
@ValidateNested({
each: true,
})
@Type(() => Type)
public items: T[];
}
return ClassName;
}
class Item {
test: string;
}
const Test = createMyClass(Item);
It might not compile, it's just an idea that works in TypeGraphQL 😄 https://19majkel94.github.io/type-graphql/docs/interfaces-and-inheritance.html#resolvers-inheritance
Ok, so I did that :
export class Parent<T> {
@Allow()
public items: T[];
}
export function createMyClass<T>(type: ClassType<T>): ClassType<Parent<T>> {
class MyClassTyped {
@Expose()
@IsNotEmpty()
@IsArray()
@ValidateNested({
each: true,
})
@Type(() => type)
public items: T[];
}
return MyClassTyped;
}
const instance = plainToClass(createMyClass<Foo>(Foo), {
items: [{
code: 'a'
// Some required properties are missing herre
}]
})
const errors = await validate(instance);
// errors is empty :(
// instance.constructor === MyClassTyped
Do you see why I don't have any error in errors ?
Is there a workaround for the generics problem now? @MichalLytek
Same problem here, impossible to find a workaround for the moment :(
I ran into this as well today and was able to solve it using mixins. It works well for my use case, but you need to create intermediate types to map the singletons returned by the class expression pattern. So it's not really viable when you have multiple properties that differ based on generics.
import 'reflect-metadata';
import { plainToClass, Type } from "class-transformer";
import { IsString, validate, ValidateNested } from "class-validator";
// We need to start out with a base class that we can extend
class BaseSearch {
@IsString()
searchId: string;
}
type Constructor<T = unknown> = new (...args: any[]) => T;
/**
* Factory functions that returns a class expression which differs based on a generic
*/
function TypedSearch<
TBase extends Constructor<BaseSearch>,
TOptions
>(Base: TBase, OptionsClass: Constructor<TOptions>) {
class TypedSearch extends Base {
@ValidateNested()
@Type(() => OptionsClass)
options: TOptions;
}
return TypedSearch
}
class LocalizedSearchOptions {
@IsString()
currency: string;
@IsString()
locale: string;
}
class LocalizedSearch extends TypedSearch(BaseSearch, LocalizedSearchOptions) {}
async function main() {
const instance = plainToClass(LocalizedSearch, {
searchId: 'test',
options: {
currency: 'EUR',
}
})
const errors = await validate(instance);
console.log(errors);
}
main();
Did anyone find a solution for this problem? Are you guys trying to offer a solution? I know it's a typescript limitation too.
This could be another workaround:
Generic class
export abstract class BatchDto<T> {
@IsArray()
@ArrayMinSize(1)
@ValidateNested({ each: true })
abstract batch: T[];
}
Implementation
export class TaskCreateDto {
@IsNotEmpty()
@IsString()
@MaxLength(20)
title: string;
@IsOptional()
@IsBoolean()
isDone: boolean;
}
export class TaskCreateManyDto extends BatchDto<TaskCreateDto> {
@Type(() => TaskCreateDto)
batch: TaskCreateDto[];
}
Have the same issue.