class-validator icon indicating copy to clipboard operation
class-validator copied to clipboard

Generic class validation

Open BenjD90 opened this issue 7 years ago • 9 comments

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:

BenjD90 avatar Sep 12 '18 11:09 BenjD90

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

MichalLytek avatar Sep 12 '18 12:09 MichalLytek

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 ?

BenjD90 avatar Sep 12 '18 14:09 BenjD90

Is there a workaround for the generics problem now? @MichalLytek

cheezone avatar Jan 17 '22 14:01 cheezone

Same problem here, impossible to find a workaround for the moment :(

alexandrebrosse avatar Feb 02 '22 21:02 alexandrebrosse

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();

janvennemann avatar May 30 '22 11:05 janvennemann

Did anyone find a solution for this problem? Are you guys trying to offer a solution? I know it's a typescript limitation too.

julimen5 avatar Jun 10 '22 19:06 julimen5

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[];
}

ju4n97 avatar Oct 16 '22 00:10 ju4n97

Have the same issue.

Expertus777 avatar Feb 14 '24 18:02 Expertus777