Skip to content

Instantly share code, notes, and snippets.

@ericjeker
Last active November 4, 2022 20:49
Show Gist options
  • Save ericjeker/08f719aae3b730c820b62136efec9708 to your computer and use it in GitHub Desktop.
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
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