├── .husky ├── commit-msg └── pre-commit ├── eslint.config.js ├── .commitlintrc.json ├── prettier.config.js ├── .lintstagedrc.json ├── .npmrc ├── lib ├── constants │ └── transformer.constant.ts ├── types │ ├── request-dto.type.ts │ └── validation-message.type.ts ├── dtos │ ├── parent.dto.ts │ └── request.dto.ts ├── index.ts ├── decorators │ └── add-request-to-body.decorator.ts ├── interceptors │ └── add-request-to-body.interceptor.ts ├── overrides │ └── class-validation.ts └── pipes │ └── validation.pipe.ts ├── sample ├── i18n │ ├── en │ │ └── validation.json │ └── vi │ │ └── validation.json ├── app.service.ts ├── configs │ ├── validate.config.ts │ └── i18n.config.ts ├── main.ts ├── app.dto.ts ├── app.module.ts └── app.controller.ts ├── renovate.json ├── .prettierrc ├── .gitignore ├── cspell.json ├── nest-cli.json ├── .github └── workflows │ └── publish.yml ├── tsconfig.json ├── package.json └── README.md /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no -- commitlint --edit "${1}" 2 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@hodfords/nestjs-eslint-config'); 2 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@hodfords/nestjs-prettier-config'); 2 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib/**/*.ts": ["cspell", "eslint --fix --max-warnings 0"] 3 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} 2 | registry=https://registry.npmjs.org/ 3 | -------------------------------------------------------------------------------- /lib/constants/transformer.constant.ts: -------------------------------------------------------------------------------- 1 | export const TRANSFORMER_EXCLUDE_KEY = 'transformer:exclude'; 2 | -------------------------------------------------------------------------------- /lib/types/request-dto.type.ts: -------------------------------------------------------------------------------- 1 | export type RequestDtoType = { 2 | query: any; 3 | params: any; 4 | }; 5 | -------------------------------------------------------------------------------- /sample/i18n/en/validation.json: -------------------------------------------------------------------------------- 1 | { 2 | "$property must be a string": "{property} must be a string" 3 | } 4 | -------------------------------------------------------------------------------- /sample/i18n/vi/validation.json: -------------------------------------------------------------------------------- 1 | { 2 | "$property must be a string": "{property} phải là một chuỗi kí tự" 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | changedFiles="$(git diff --name-only --cached)" 2 | npm run cspell --no-must-find-files ${changedFiles} 3 | npm run lint-staged 4 | -------------------------------------------------------------------------------- /lib/dtos/parent.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsOptional } from 'class-validator'; 2 | 3 | export abstract class ParentDto { 4 | @IsOptional() 5 | protected parentDto: any; 6 | } 7 | -------------------------------------------------------------------------------- /sample/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "prConcurrentLimit": 5, 5 | "assignees": ["hodfords_dung_senior_dev"], 6 | "labels": ["renovate"] 7 | } 8 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | import './overrides/class-validation'; 2 | export * from './dtos/parent.dto'; 3 | export * from './dtos/request.dto'; 4 | export * from './pipes/validation.pipe'; 5 | export * from './decorators/add-request-to-body.decorator'; 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 120, 4 | "proseWrap": "always", 5 | "tabWidth": 4, 6 | "useTabs": false, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "bracketSameLine": false, 10 | "semi": true, 11 | "endOfLine": "auto" 12 | } -------------------------------------------------------------------------------- /lib/dtos/request.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsOptional } from 'class-validator'; 2 | import { ParentDto } from './parent.dto'; 3 | import { RequestDtoType } from '../types/request-dto.type'; 4 | 5 | export abstract class RequestDto extends ParentDto { 6 | @IsOptional() 7 | protected requestDto?: RequestDtoType; 8 | } 9 | -------------------------------------------------------------------------------- /lib/types/validation-message.type.ts: -------------------------------------------------------------------------------- 1 | import { ValidationArguments } from 'class-validator'; 2 | 3 | export type ValidationMessage = { 4 | detail: { 5 | property: string; 6 | target: string; 7 | value: any; 8 | }; 9 | message: string | ((args: ValidationArguments) => string); 10 | }; 11 | -------------------------------------------------------------------------------- /lib/decorators/add-request-to-body.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, UseInterceptors } from '@nestjs/common'; 2 | import { AddRequestToBodyInterceptor } from '../interceptors/add-request-to-body.interceptor'; 3 | 4 | export function AddRequestToBody(): any { 5 | return applyDecorators(UseInterceptors(AddRequestToBodyInterceptor)); 6 | } 7 | -------------------------------------------------------------------------------- /sample/configs/validate.config.ts: -------------------------------------------------------------------------------- 1 | import { ValidateException } from '@hodfords/nestjs-exception'; 2 | import { ValidationPipe } from '@hodfords/nestjs-validation'; 3 | 4 | export const validateConfig = new ValidationPipe({ 5 | whitelist: true, 6 | stopAtFirstError: true, 7 | exceptionFactory: (errors): ValidateException => new ValidateException(errors) 8 | }); 9 | -------------------------------------------------------------------------------- /sample/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { validateConfig } from './configs/validate.config'; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | app.useGlobalPipes(validateConfig); 8 | await app.listen(3000); 9 | console.log('App listening on port 3000'); 10 | } 11 | bootstrap(); 12 | -------------------------------------------------------------------------------- /sample/app.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, ValidateIf } from 'class-validator'; 2 | import { RequestDto } from '../lib/dtos/request.dto'; 3 | 4 | export class AppDto { 5 | @IsString() 6 | stringValue: string; 7 | } 8 | 9 | export class AddRequestToBodyDto extends RequestDto { 10 | @IsString() 11 | @ValidateIf((dto: AddRequestToBodyDto) => { 12 | return !!dto.requestDto.params.id; 13 | }) 14 | stringValue: string; 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # OS 14 | .DS_Store 15 | 16 | # Tests 17 | /coverage 18 | /.nyc_output 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json 35 | 36 | .env -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en", 4 | "words": [ 5 | "nestjs", 6 | "diginexhk", 7 | "metadatas", 8 | "dtos", 9 | "metatype", 10 | "postbuild", 11 | "hodfords", 12 | "commitlint", 13 | "classpath", 14 | "dotenv", 15 | "pids", 16 | "diginex", 17 | "typeorm", 18 | "npmjs" 19 | ], 20 | "flagWords": ["hte"], 21 | "ignorePaths": ["node_modules", "test", "*.spec.ts", "cspell.json", "dist"] 22 | } 23 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "sample", 4 | "projects": { 5 | "nestjs-storage": { 6 | "type": "library", 7 | "root": "lib", 8 | "entryFile": "index", 9 | "sourceRoot": "lib" 10 | } 11 | }, 12 | "compilerOptions": { 13 | "webpack": false, 14 | "assets": [ 15 | { 16 | "include": "../lib/public/**", 17 | "watchAssets": true 18 | }, 19 | { 20 | "include": "../sample/i18n/**", 21 | "watchAssets": true 22 | } 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | lint: 8 | uses: hodfords-solutions/actions/.github/workflows/lint.yaml@main 9 | build: 10 | uses: hodfords-solutions/actions/.github/workflows/publish.yaml@main 11 | with: 12 | build_path: dist/lib 13 | secrets: 14 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 15 | update-docs: 16 | uses: hodfords-solutions/actions/.github/workflows/update-doc.yaml@main 17 | needs: build 18 | secrets: 19 | DOC_SSH_PRIVATE_KEY: ${{ secrets.DOC_SSH_PRIVATE_KEY }} -------------------------------------------------------------------------------- /sample/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { i18nConfig } from './configs/i18n.config'; 5 | import { APP_FILTER } from '@nestjs/core'; 6 | import { HttpExceptionFilter } from '@hodfords/nestjs-exception'; 7 | 8 | @Module({ 9 | imports: [i18nConfig], 10 | controllers: [AppController], 11 | providers: [ 12 | AppService, 13 | { 14 | provide: APP_FILTER, 15 | useClass: HttpExceptionFilter 16 | } 17 | ] 18 | }) 19 | export class AppModule {} 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "removeComments": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "allowSyntheticDefaultImports": true, 11 | "target": "ES2022", 12 | "sourceMap": true, 13 | "outDir": "dist", 14 | "baseUrl": "./", 15 | "incremental": true, 16 | "paths": { 17 | "@hodfords/nestjs-validation": ["lib"] 18 | } 19 | }, 20 | "include": [ 21 | "lib", 22 | "sample" 23 | ], 24 | "exclude": [ 25 | "tests", 26 | "dist", 27 | "node_modules" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /sample/configs/i18n.config.ts: -------------------------------------------------------------------------------- 1 | import { HeaderResolver, QueryResolver } from 'nestjs-i18n'; 2 | import path from 'path'; 3 | import { TranslationModule, RequestResolver } from '@hodfords/nestjs-cls-translation'; 4 | 5 | export const i18nConfig = TranslationModule.forRoot({ 6 | fallbackLanguage: 'en', 7 | loaderOptions: { 8 | path: path.join('dist', 'i18n/'), 9 | watch: true 10 | }, 11 | resolvers: [new HeaderResolver(['language']), new QueryResolver(['language'])], 12 | defaultLanguageKey: 'language', 13 | clsResolvers: [ 14 | new RequestResolver([ 15 | { key: 'language', type: 'query' }, 16 | { key: 'language', type: 'headers' } 17 | ]) 18 | ] 19 | }); 20 | -------------------------------------------------------------------------------- /sample/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Post, Put } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | import { AddRequestToBodyDto, AppDto } from './app.dto'; 4 | import { AddRequestToBody } from '../lib/decorators/add-request-to-body.decorator'; 5 | 6 | @Controller() 7 | export class AppController { 8 | constructor(private readonly appService: AppService) {} 9 | 10 | @Get() 11 | getHello(): string { 12 | return this.appService.getHello(); 13 | } 14 | 15 | @Post() 16 | sayHello(@Body() dto: AppDto): string { 17 | return dto.stringValue; 18 | } 19 | 20 | @Put(':id') 21 | @AddRequestToBody() 22 | addRequestToBody(@Body() dto: AddRequestToBodyDto): AddRequestToBodyDto { 23 | return dto; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/interceptors/add-request-to-body.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { isObject } from '@nestjs/common/utils/shared.utils'; 3 | import { Observable } from 'rxjs'; 4 | 5 | @Injectable() 6 | export class AddRequestToBodyInterceptor implements NestInterceptor { 7 | intercept(context: ExecutionContext, next: CallHandler): Observable { 8 | const request = context.switchToHttp().getRequest(); 9 | if ( 10 | (request.method === 'PUT' || request.method === 'PATCH' || request.method === 'POST') && 11 | request.body && 12 | isObject(request.body) 13 | ) { 14 | request.body.requestDto = { 15 | params: request.params, 16 | query: request.query, 17 | user: request.user 18 | }; 19 | } 20 | return next.handle(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/overrides/class-validation.ts: -------------------------------------------------------------------------------- 1 | import { ValidationUtils } from 'class-validator/cjs/validation/ValidationUtils.js'; 2 | 3 | export function constraintToString(constraint: unknown): string { 4 | if (Array.isArray(constraint)) { 5 | return constraint.join(', '); 6 | } 7 | return `${constraint}`; 8 | } 9 | 10 | ValidationUtils.replaceMessageSpecialTokens = function ( 11 | message: string | ((args: any) => string), 12 | validationArguments: any 13 | ): any { 14 | const detail: any = { 15 | property: validationArguments.property, 16 | target: validationArguments.targetName, 17 | value: validationArguments.value 18 | }; 19 | 20 | if (validationArguments.constraints instanceof Array) { 21 | validationArguments.constraints.forEach((constraint: any, index: number) => { 22 | detail[`constraint${index + 1}`] = constraintToString(constraint); 23 | }); 24 | } 25 | 26 | return { message, detail }; 27 | }; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hodfords/nestjs-validation", 3 | "version": "11.1.1", 4 | "description": "A utility for simplifying validation and providing translated error messages in NestJS applications", 5 | "main": "index.js", 6 | "scripts": { 7 | "prebuild": "rimraf dist", 8 | "build": "tsc", 9 | "postbuild": "cp package.json dist/lib && cp README.md dist/lib && cp .npmrc dist/lib", 10 | "prepare": "is-ci || husky", 11 | "format": "prettier --write \"lib/**/*.ts\" \"sample/**/*.ts\"", 12 | "cspell": "cspell", 13 | "lint": "eslint \"lib/**/*.ts\" \"sample/**/*.ts\" --fix --max-warnings 0", 14 | "lint-staged": "lint-staged", 15 | "start": "nest start", 16 | "start:dev": "npm run prebuild && nest start --watch" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/hodfords-solutions/nestjs-validation" 21 | }, 22 | "keywords": [], 23 | "author": "", 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/hodfords-solutions/nestjs-validation/issues" 27 | }, 28 | "homepage": "https://opensource.hodfords.uk/", 29 | "devDependencies": { 30 | "@commitlint/cli": "20.1.0", 31 | "@commitlint/config-conventional": "20.0.0", 32 | "@grpc/grpc-js": "^1.14.1", 33 | "@hodfords/nestjs-eslint-config": "11.0.2", 34 | "@hodfords/nestjs-exception": "11.0.2", 35 | "@hodfords/nestjs-prettier-config": "11.0.1", 36 | "@hodfords/nestjs-cls-translation": "^11.1.0", 37 | "@nestjs/cli": "11.0.14", 38 | "@nestjs/common": "11.1.9", 39 | "@nestjs/core": "11.1.9", 40 | "@nestjs/platform-express": "11.1.9", 41 | "@types/node": "24.10.1", 42 | "class-transformer": "0.5.1", 43 | "class-validator": "0.14.3", 44 | "cspell": "9.3.2", 45 | "eslint": "9.39.1", 46 | "husky": "9.1.7", 47 | "is-ci": "4.1.0", 48 | "lint-staged": "16.2.7", 49 | "lodash": "^4.17.21", 50 | "reflect-metadata": "0.2.2", 51 | "rimraf": "6.1.2", 52 | "typeorm": "^0.3.27", 53 | "typescript": "5.9.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Hodfords Logo 3 |

4 | 5 |

nestjs-validation enhances validation in your NestJS projects by providing a customized ValidationPipe that returns custom error messages. This library simplifies error handling by offering localized and user-friendly responses

6 | 7 | ## Installation 🤖 8 | 9 | Install the `nestjs-validation` package with: 10 | 11 | ```bash 12 | npm install @hodfords/nestjs-validation --save 13 | ``` 14 | 15 | ## Usage 🚀 16 | 17 | First, create an instance of `ValidationPipe` with the desired configuration: 18 | 19 | ```typescript 20 | import { ValidationPipe } from '@hodfords/nestjs-validation'; 21 | import { ValidateException } from '@hodfords/nestjs-exception'; 22 | 23 | export const validateConfig = new ValidationPipe({ 24 | whitelist: true, 25 | stopAtFirstError: true, 26 | forbidUnknownValues: false, 27 | exceptionFactory: (errors): ValidateException => new ValidateException(errors) 28 | }); 29 | ``` 30 | 31 | Next, set the validation configuration globally in your bootstrap function: 32 | 33 | ```typescript 34 | async function bootstrap() { 35 | const app = await NestFactory.create(AppModule); 36 | app.useGlobalPipes(validateConfig); 37 | await app.listen(3000); 38 | } 39 | ``` 40 | 41 | ### Customize Validation Error 42 | 43 | The original error message provides basic information but lacks detail. With **nestjs-validation**, you can enhance these errors by adding meaningful context, such as the field’s property name, value, and target object. 44 | 45 | **Original Validation Error** 46 | 47 | ```javascript 48 | ValidationError { 49 | target: AppDto { stringValue: undefined }, 50 | value: undefined, 51 | property: 'stringValue', 52 | children: [], 53 | constraints: { isString: 'stringValue must be a string' } 54 | } 55 | ``` 56 | 57 | **Customized Validation Error** 58 | 59 | ```javascript 60 | ValidationError { 61 | target: AppDto { stringValue: undefined }, 62 | value: undefined, 63 | property: 'stringValue', 64 | children: [], 65 | constraints: { 66 | isString: { 67 | message: '$property must be a string', 68 | detail: { property: 'stringValue', target: 'AppDto', value: undefined } 69 | } 70 | } 71 | } 72 | ``` 73 | 74 | ### Exception 75 | 76 | When combined with [nestjs-exception](https://www.npmjs.com/package/@hodfords/nestjs-exception), errors are translated into localized messages: 77 | 78 | ```json 79 | { 80 | "message": "Validate Exception", 81 | "errors": { 82 | "stringValue": { 83 | "messages": ["String Value must be a string"] 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | ## License 📝 90 | 91 | This project is licensed under the MIT License 92 | -------------------------------------------------------------------------------- /lib/pipes/validation.pipe.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArgumentMetadata, 3 | Injectable, 4 | Optional, 5 | ValidationPipe as BaseValidationPipe, 6 | ValidationPipeOptions 7 | } from '@nestjs/common'; 8 | import { isObject } from '@nestjs/common/utils/shared.utils'; 9 | import { ParentDto } from '../dtos/parent.dto'; 10 | import { RequestDto } from '../dtos/request.dto'; 11 | import { TRANSFORMER_EXCLUDE_KEY } from '../constants/transformer.constant'; 12 | 13 | @Injectable() 14 | export class ValidationPipe extends BaseValidationPipe { 15 | private classValidator: any; 16 | private classTransformer: any; 17 | 18 | constructor(@Optional() private options?: ValidationPipeOptions) { 19 | super(options); 20 | this.classValidator = this.loadValidator(); 21 | this.classTransformer = this.loadTransformer(); 22 | } 23 | 24 | public async transform(value: any, metadata: ArgumentMetadata): Promise { 25 | if (this.expectedType) { 26 | metadata = { ...metadata, metatype: this.expectedType }; 27 | } 28 | const metatype = metadata.metatype; 29 | if (!metatype || !this.toValidate(metadata)) { 30 | return this.isTransformEnabled ? this.transformPrimitive(value, metadata) : value; 31 | } 32 | const originalValue = value; 33 | value = this.toEmptyIfNil(value, metatype); 34 | 35 | const isNil = value !== originalValue; 36 | const isPrimitive = this.isPrimitive(value); 37 | this.stripProtoKeys(value); 38 | let entity = this.plainToClass(metatype, value); 39 | const originalEntity = entity; 40 | const isCtorNotEqual = entity.constructor !== metatype; 41 | 42 | if (isCtorNotEqual && !isPrimitive) { 43 | entity.constructor = metatype; 44 | } else if (isCtorNotEqual) { 45 | entity = { constructor: metatype }; 46 | } 47 | const errors = await this.classValidator.validate(entity, this.validatorOptions); 48 | if (errors.length > 0) { 49 | throw await this.exceptionFactory(errors); 50 | } 51 | if (isPrimitive) { 52 | entity = originalEntity; 53 | } 54 | this.removeRequestData(entity); 55 | if (this.isTransformEnabled) { 56 | return entity; 57 | } 58 | if (isNil) { 59 | return originalValue; 60 | } 61 | return Object.keys(this.validatorOptions).length > 0 62 | ? this.classTransformer.classToPlain(entity, this.transformOptions) 63 | : value; 64 | } 65 | 66 | private removeRequestData(entity): any { 67 | if (Array.isArray(entity)) { 68 | for (const item of entity) { 69 | this.removeRequestData(item); 70 | } 71 | } else if (isObject(entity)) { 72 | delete entity['requestDto']; 73 | delete entity['parentDto']; 74 | for (const key in entity) { 75 | this.removeRequestData(entity[key]); 76 | } 77 | } 78 | } 79 | 80 | private plainToClass(metatype, value): any { 81 | const entity = this.classTransformer.plainToClass(metatype, value, this.transformOptions); 82 | this.addRequestToObject(entity, null, entity.requestDto); 83 | this.removeExcludedFields(entity, metatype); 84 | 85 | return entity; 86 | } 87 | 88 | private addRequestToObject(entity, parent, request): any { 89 | if (Array.isArray(entity)) { 90 | for (const item of entity) { 91 | this.addRequestToObject(item, entity, request); 92 | } 93 | } else if (isObject(entity)) { 94 | if (entity instanceof RequestDto) { 95 | (entity as any).requestDto = request; 96 | } 97 | if (entity instanceof ParentDto) { 98 | (entity as any).parentDto = parent; 99 | } 100 | for (const key in entity) { 101 | if (key !== 'parentDto' && key !== 'requestDto') { 102 | this.addRequestToObject(entity[key], entity, request); 103 | } 104 | } 105 | } 106 | } 107 | 108 | private removeExcludedFields(entity, metatype): any { 109 | if (!metatype || !isObject(entity)) { 110 | return; 111 | } 112 | 113 | const metadata = Reflect.getMetadata(TRANSFORMER_EXCLUDE_KEY, metatype) || []; 114 | const excludedFields = metadata.filter((item) => !item.condition(entity)).map((item) => item.propertyName); 115 | for (const field of excludedFields) { 116 | delete entity[field]; 117 | } 118 | 119 | for (const key in entity) { 120 | const value = entity[key]; 121 | 122 | if (['parentDto', 'requestDto'].includes(key)) { 123 | continue; 124 | } 125 | if (Array.isArray(value)) { 126 | for (const item of value) { 127 | this.removeExcludedFields(item, item?.constructor); 128 | } 129 | } else if (isObject(value)) { 130 | this.removeExcludedFields(value, value.constructor); 131 | } 132 | } 133 | } 134 | } 135 | --------------------------------------------------------------------------------