Last active
November 4, 2022 20:49
-
-
Save ericjeker/08f719aae3b730c820b62136efec9708 to your computer and use it in GitHub Desktop.
Partial rewrite of Nest.js ClassSerializerInterceptor to integrate the findAndCount result of TypeORM and serialize the results using class-transformer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Observable } from 'rxjs'; | |
import { map } from 'rxjs/operators'; | |
import { | |
CallHandler, | |
ExecutionContext, | |
Inject, | |
Injectable, | |
NestInterceptor, | |
Optional, | |
} from '@nestjs/common'; | |
import { ClassTransformOptions } from 'class-transformer'; | |
import { loadPackage } from '@nestjs/common/utils/load-package.util'; | |
import { isObject } from '@nestjs/common/utils/shared.utils'; | |
import { CLASS_SERIALIZER_OPTIONS } from '@nestjs/common/serializer/class-serializer.constants'; | |
let classTransformer: any = {}; | |
export interface PlainLiteralObject { | |
[key: string]: any; | |
} | |
// NOTE (external) | |
// We need to deduplicate them here due to the circular dependency | |
// between core and common packages | |
const REFLECTOR = 'Reflector'; | |
/** | |
* Partial rewrite of https://github.com/nestjs/nest/blob/master/packages/common/serializer/class-serializer.interceptor.ts | |
* to integrate the findAndCount result of TypeORM and serialize the results | |
* using class-transformer. | |
*/ | |
@Injectable() | |
export class ClassSerializerInterceptor implements NestInterceptor { | |
constructor( | |
@Inject(REFLECTOR) protected readonly reflector: any, | |
@Optional() protected readonly defaultOptions: ClassTransformOptions = {}, | |
) { | |
classTransformer = loadPackage( | |
'class-transformer', | |
'ClassSerializerInterceptor', | |
() => require('class-transformer'), | |
); | |
require('class-transformer'); | |
} | |
intercept(context: ExecutionContext, next: CallHandler): Observable<any> { | |
const contextOptions = this.getContextOptions(context); | |
const options = { | |
...this.defaultOptions, | |
...contextOptions, | |
}; | |
return next | |
.handle() | |
.pipe( | |
map((res: PlainLiteralObject | Array<PlainLiteralObject> | [Array<PlainLiteralObject>, number]) => | |
this.serialize(res, options), | |
), | |
); | |
} | |
serialize( | |
response: PlainLiteralObject | Array<PlainLiteralObject> | [Array<PlainLiteralObject>, number], | |
options: ClassTransformOptions, | |
): PlainLiteralObject | PlainLiteralObject[] | [PlainLiteralObject[], number] { | |
const isArray = Array.isArray(response); | |
// if the response is not an object or an array we return as is | |
if (!isObject(response) && !isArray) { | |
return response; | |
} | |
// if it's an object (not an array) we apply a plain transformation | |
if (!isArray) { | |
return this.transformToPlain(response, options); | |
} | |
// if it's an array, we check if the array is of type [Array<PlainLiteralObject>, number] | |
if ((response as PlainLiteralObject[]).length === 2 && Array.isArray(response[0]) && Number.isInteger(response[1])) { | |
response[0] = this.transformArray((response[0] as PlainLiteralObject[]), options); | |
return [response[0], response[1]]; | |
} else { | |
// otherwise, it's a simple array so we just transform it and return it | |
return this.transformArray(response as PlainLiteralObject[], options); | |
} | |
} | |
/** | |
* TransformArray simply iterate the response and apply a transformToPlain. | |
* | |
* @param response | |
* @param options | |
* @private | |
*/ | |
private transformArray(response: Array<PlainLiteralObject>, options: ClassTransformOptions): PlainLiteralObject[] { | |
return (response as PlainLiteralObject[]).map(item => | |
this.transformToPlain(item, options)); | |
} | |
/** | |
* Apply a transformation from an object to a plain object using class-transformer. | |
* | |
* @param plainOrClass | |
* @param options | |
*/ | |
transformToPlain( | |
plainOrClass: any, | |
options: ClassTransformOptions, | |
): PlainLiteralObject { | |
return plainOrClass && plainOrClass.constructor !== Object | |
? classTransformer.classToPlain(plainOrClass, options) | |
: plainOrClass; | |
} | |
private getContextOptions( | |
context: ExecutionContext, | |
): ClassTransformOptions | undefined { | |
return ( | |
this.reflectSerializeMetadata(context.getHandler()) || | |
this.reflectSerializeMetadata(context.getClass()) | |
); | |
} | |
private reflectSerializeMetadata( | |
obj: object | Function, | |
): ClassTransformOptions | undefined { | |
return this.reflector.get(CLASS_SERIALIZER_OPTIONS, obj); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment